mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 07:58:22 -05:00
consistent auth token for validator apis (#13747)
* wip * fixing tests * adding more tests especially to handle legacy * fixing linting * fixing deepsource issues and flags * fixing some deepsource issues,pathing issues, and logs * some review items * adding additional review feedback * updating to follow updates from https://github.com/ethereum/keymanager-APIs/pull/74 * adjusting functions to match changes in keymanagers PR * Update validator/rpc/auth_token.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/auth_token.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/auth_token.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * review feedback --------- Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
@@ -639,6 +639,17 @@ func (c *ValidatorClient) registerRPCService(router *mux.Router) error {
|
||||
walletDir := c.cliCtx.String(flags.WalletDirFlag.Name)
|
||||
grpcHeaders := c.cliCtx.String(flags.GrpcHeadersFlag.Name)
|
||||
clientCert := c.cliCtx.String(flags.CertFlag.Name)
|
||||
|
||||
authTokenPath := c.cliCtx.String(flags.AuthTokenPathFlag.Name)
|
||||
// if no auth token path flag was passed try to set a default value
|
||||
if authTokenPath == "" {
|
||||
authTokenPath = flags.AuthTokenPathFlag.Value
|
||||
// if a wallet dir is passed without an auth token then override the default with the wallet dir
|
||||
if walletDir != "" {
|
||||
authTokenPath = filepath.Join(walletDir, api.AuthTokenFileName)
|
||||
}
|
||||
}
|
||||
|
||||
server := rpc.NewServer(c.cliCtx.Context, &rpc.Config{
|
||||
ValDB: c.db,
|
||||
Host: rpcHost,
|
||||
@@ -648,6 +659,7 @@ func (c *ValidatorClient) registerRPCService(router *mux.Router) error {
|
||||
SyncChecker: vs,
|
||||
GenesisFetcher: vs,
|
||||
NodeGatewayEndpoint: nodeGatewayEndpoint,
|
||||
AuthTokenPath: authTokenPath,
|
||||
WalletDir: walletDir,
|
||||
Wallet: c.wallet,
|
||||
ValidatorGatewayHost: validatorGatewayHost,
|
||||
|
||||
@@ -38,7 +38,6 @@ go_library(
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/validator:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//io/logs:go_default_library",
|
||||
@@ -148,6 +147,7 @@ go_test(
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
|
||||
@@ -15,32 +15,24 @@ import (
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/rand"
|
||||
"github.com/prysmaticlabs/prysm/v5/api"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/io/file"
|
||||
)
|
||||
|
||||
const (
|
||||
AuthTokenFileName = "auth-token"
|
||||
)
|
||||
|
||||
// CreateAuthToken generates a new jwt key, token and writes them
|
||||
// to a file in the specified directory. Also, it logs out a prepared URL
|
||||
// for the user to navigate to and authenticate with the Prysm web interface.
|
||||
func CreateAuthToken(walletDirPath, validatorWebAddr string) error {
|
||||
jwtKey, err := createRandomJWTSecret()
|
||||
func CreateAuthToken(authPath, validatorWebAddr string) error {
|
||||
token, err := api.GenerateRandomHexString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token, err := createTokenString(jwtKey)
|
||||
if err != nil {
|
||||
log.Infof("Generating auth token and saving it to %s", authPath)
|
||||
if err := saveAuthToken(authPath, token); err != nil {
|
||||
return err
|
||||
}
|
||||
authTokenPath := filepath.Join(walletDirPath, AuthTokenFileName)
|
||||
log.Infof("Generating auth token and saving it to %s", authTokenPath)
|
||||
if err := saveAuthToken(walletDirPath, jwtKey, token); err != nil {
|
||||
return err
|
||||
}
|
||||
logValidatorWebAuth(validatorWebAddr, token, authTokenPath)
|
||||
logValidatorWebAuth(validatorWebAddr, token, authPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -49,18 +41,18 @@ func CreateAuthToken(walletDirPath, validatorWebAddr string) error {
|
||||
// user via stdout and the validator client should then attempt to open the default
|
||||
// browser. The web interface authenticates by looking for this token in the query parameters
|
||||
// of the URL. This token is then used as the bearer token for jwt auth.
|
||||
func (s *Server) initializeAuthToken(walletDir string) (string, error) {
|
||||
authTokenFile := filepath.Join(walletDir, AuthTokenFileName)
|
||||
exists, err := file.Exists(authTokenFile, file.Regular)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not check if file exists: %s", authTokenFile)
|
||||
func (s *Server) initializeAuthToken() error {
|
||||
if s.authTokenPath == "" {
|
||||
return errors.New("auth token path is empty")
|
||||
}
|
||||
exists, err := file.Exists(s.authTokenPath, file.Regular)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not check if file %s exists", s.authTokenPath)
|
||||
}
|
||||
|
||||
if exists {
|
||||
// #nosec G304
|
||||
f, err := os.Open(authTokenFile)
|
||||
f, err := os.Open(filepath.Clean(s.authTokenPath))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
@@ -69,24 +61,18 @@ func (s *Server) initializeAuthToken(walletDir string) (string, error) {
|
||||
}()
|
||||
secret, token, err := readAuthTokenFile(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
s.jwtSecret = secret
|
||||
return token, nil
|
||||
s.authToken = token
|
||||
return nil
|
||||
}
|
||||
jwtKey, err := createRandomJWTSecret()
|
||||
token, err := api.GenerateRandomHexString()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
s.jwtSecret = jwtKey
|
||||
token, err := createTokenString(s.jwtSecret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := saveAuthToken(walletDir, jwtKey, token); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return token, nil
|
||||
s.authToken = token
|
||||
return saveAuthToken(s.authTokenPath, token)
|
||||
}
|
||||
|
||||
func (s *Server) refreshAuthTokenFromFileChanges(ctx context.Context, authTokenPath string) {
|
||||
@@ -106,16 +92,20 @@ func (s *Server) refreshAuthTokenFromFileChanges(ctx context.Context, authTokenP
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-watcher.Events:
|
||||
case event := <-watcher.Events:
|
||||
if event.Op.String() == "REMOVE" {
|
||||
log.Error("Auth Token was removed! Restart the validator client to regenerate a token")
|
||||
s.authToken = ""
|
||||
continue
|
||||
}
|
||||
// If a file was modified, we attempt to read that file
|
||||
// and parse it into our accounts store.
|
||||
token, err := s.initializeAuthToken(s.walletDir)
|
||||
if err != nil {
|
||||
if err := s.initializeAuthToken(); err != nil {
|
||||
log.WithError(err).Errorf("Could not watch for file changes for: %s", authTokenPath)
|
||||
continue
|
||||
}
|
||||
validatorWebAddr := fmt.Sprintf("%s:%d", s.validatorGatewayHost, s.validatorGatewayPort)
|
||||
logValidatorWebAuth(validatorWebAddr, token, authTokenPath)
|
||||
logValidatorWebAuth(validatorWebAddr, s.authToken, authTokenPath)
|
||||
case err := <-watcher.Errors:
|
||||
log.WithError(err).Errorf("Could not watch for file changes for: %s", authTokenPath)
|
||||
case <-ctx.Done():
|
||||
@@ -124,7 +114,7 @@ func (s *Server) refreshAuthTokenFromFileChanges(ctx context.Context, authTokenP
|
||||
}
|
||||
}
|
||||
|
||||
func logValidatorWebAuth(validatorWebAddr, token string, tokenPath string) {
|
||||
func logValidatorWebAuth(validatorWebAddr, token, tokenPath string) {
|
||||
webAuthURLTemplate := "http://%s/initialize?token=%s"
|
||||
webAuthURL := fmt.Sprintf(
|
||||
webAuthURLTemplate,
|
||||
@@ -136,18 +126,11 @@ func logValidatorWebAuth(validatorWebAddr, token string, tokenPath string) {
|
||||
"the Prysm web interface",
|
||||
)
|
||||
log.Info(webAuthURL)
|
||||
log.Infof("Validator CLient JWT for RPC and REST authentication set at:%s", tokenPath)
|
||||
log.Infof("Validator Client auth token for gRPC and REST authentication set at %s", tokenPath)
|
||||
}
|
||||
|
||||
func saveAuthToken(walletDirPath string, jwtKey []byte, token string) error {
|
||||
hashFilePath := filepath.Join(walletDirPath, AuthTokenFileName)
|
||||
func saveAuthToken(tokenPath string, token string) error {
|
||||
bytesBuf := new(bytes.Buffer)
|
||||
if _, err := bytesBuf.WriteString(fmt.Sprintf("%x", jwtKey)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := bytesBuf.WriteString("\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := bytesBuf.WriteString(token); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -155,34 +138,61 @@ func saveAuthToken(walletDirPath string, jwtKey []byte, token string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := file.MkdirAll(walletDirPath); err != nil {
|
||||
return errors.Wrapf(err, "could not create directory %s", walletDirPath)
|
||||
if err := file.MkdirAll(filepath.Dir(tokenPath)); err != nil {
|
||||
return errors.Wrapf(err, "could not create directory %s", filepath.Dir(tokenPath))
|
||||
}
|
||||
|
||||
if err := file.WriteFile(hashFilePath, bytesBuf.Bytes()); err != nil {
|
||||
return errors.Wrapf(err, "could not write to file %s", hashFilePath)
|
||||
if err := file.WriteFile(tokenPath, bytesBuf.Bytes()); err != nil {
|
||||
return errors.Wrapf(err, "could not write to file %s", tokenPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAuthTokenFile(r io.Reader) (secret []byte, token string, err error) {
|
||||
br := bufio.NewReader(r)
|
||||
var jwtKeyHex string
|
||||
jwtKeyHex, err = br.ReadString('\n')
|
||||
if err != nil {
|
||||
return
|
||||
func readAuthTokenFile(r io.Reader) ([]byte, string, error) {
|
||||
scanner := bufio.NewScanner(r)
|
||||
var lines []string
|
||||
var secret []byte
|
||||
var token string
|
||||
// Scan the file and collect lines, excluding empty lines
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.TrimSpace(line) != "" {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
secret, err = hex.DecodeString(strings.TrimSpace(jwtKeyHex))
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
// Check for scanning errors
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
tokenBytes, _, err := br.ReadLine()
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
// Process based on the number of lines, excluding empty ones
|
||||
switch len(lines) {
|
||||
case 1:
|
||||
// If there is only one line, interpret it as the token
|
||||
token = strings.TrimSpace(lines[0])
|
||||
case 2:
|
||||
// TODO: Deprecate after a few releases
|
||||
// For legacy files
|
||||
// If there are two lines, the first is the jwt key and the second is the token
|
||||
jwtKeyHex := strings.TrimSpace(lines[0])
|
||||
s, err := hex.DecodeString(jwtKeyHex)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrapf(err, "could not decode JWT secret")
|
||||
}
|
||||
secret = bytesutil.SafeCopyBytes(s)
|
||||
token = strings.TrimSpace(lines[1])
|
||||
log.Warn("Auth token is a legacy file and should be regenerated.")
|
||||
default:
|
||||
return nil, "", errors.New("Auth token file format has multiple lines, please update the auth token to a single line that is a 256 bit hex string")
|
||||
}
|
||||
token = strings.TrimSpace(string(tokenBytes))
|
||||
return
|
||||
if err := api.ValidateAuthToken(token); err != nil {
|
||||
log.WithError(err).Warn("Auth token does not follow our standards and should be regenerated either \n" +
|
||||
"1. by removing the current token file and restarting \n" +
|
||||
"2. using the `validator web generate-auth-token` command. \n" +
|
||||
"Tokens can be generated through the `validator web generate-auth-token` command")
|
||||
}
|
||||
return secret, token, nil
|
||||
}
|
||||
|
||||
// Creates a JWT token string using the JWT key.
|
||||
@@ -195,16 +205,3 @@ func createTokenString(jwtKey []byte) (string, error) {
|
||||
}
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
func createRandomJWTSecret() ([]byte, error) {
|
||||
r := rand.NewGenerator()
|
||||
jwtKey := make([]byte, 32)
|
||||
n, err := r.Read(jwtKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != len(jwtKey) {
|
||||
return nil, errors.New("could not create appropriately sized random JWT secret")
|
||||
}
|
||||
return jwtKey, nil
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/prysmaticlabs/prysm/v5/api"
|
||||
"github.com/prysmaticlabs/prysm/v5/io/file"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
@@ -24,11 +30,14 @@ func setupWalletDir(t testing.TB) string {
|
||||
func TestServer_AuthenticateUsingExistingToken(t *testing.T) {
|
||||
// Initializing for the first time, there is no auth token file in
|
||||
// the wallet directory, so we generate a jwt token and secret from scratch.
|
||||
srv := &Server{}
|
||||
walletDir := setupWalletDir(t)
|
||||
token, err := srv.initializeAuthToken(walletDir)
|
||||
authTokenPath := filepath.Join(walletDir, api.AuthTokenFileName)
|
||||
srv := &Server{
|
||||
authTokenPath: authTokenPath,
|
||||
}
|
||||
|
||||
err := srv.initializeAuthToken()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, len(srv.jwtSecret) > 0)
|
||||
|
||||
unaryInfo := &grpc.UnaryServerInfo{
|
||||
FullMethod: "Proto.CreateWallet",
|
||||
@@ -37,78 +46,173 @@ func TestServer_AuthenticateUsingExistingToken(t *testing.T) {
|
||||
return nil, nil
|
||||
}
|
||||
ctxMD := map[string][]string{
|
||||
"authorization": {"Bearer " + token},
|
||||
"authorization": {"Bearer " + srv.authToken},
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = metadata.NewIncomingContext(ctx, ctxMD)
|
||||
_, err = srv.JWTInterceptor()(ctx, "xyz", unaryInfo, unaryHandler)
|
||||
_, err = srv.AuthTokenInterceptor()(ctx, "xyz", unaryInfo, unaryHandler)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Next up, we make the same request but reinitialize the server and we should still
|
||||
// pass with the same auth token.
|
||||
srv = &Server{}
|
||||
_, err = srv.initializeAuthToken(walletDir)
|
||||
srv = &Server{
|
||||
authTokenPath: authTokenPath,
|
||||
}
|
||||
err = srv.initializeAuthToken()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, len(srv.jwtSecret) > 0)
|
||||
_, err = srv.JWTInterceptor()(ctx, "xyz", unaryInfo, unaryHandler)
|
||||
_, err = srv.AuthTokenInterceptor()(ctx, "xyz", unaryInfo, unaryHandler)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestServer_RefreshJWTSecretOnFileChange(t *testing.T) {
|
||||
func TestServer_RefreshAuthTokenOnFileChange(t *testing.T) {
|
||||
// Initializing for the first time, there is no auth token file in
|
||||
// the wallet directory, so we generate a jwt token and secret from scratch.
|
||||
srv := &Server{}
|
||||
walletDir := setupWalletDir(t)
|
||||
_, err := srv.initializeAuthToken(walletDir)
|
||||
require.NoError(t, err)
|
||||
currentSecret := srv.jwtSecret
|
||||
require.Equal(t, true, len(currentSecret) > 0)
|
||||
authTokenPath := filepath.Join(walletDir, api.AuthTokenFileName)
|
||||
srv := &Server{
|
||||
authTokenPath: authTokenPath,
|
||||
}
|
||||
|
||||
authTokenPath := filepath.Join(walletDir, AuthTokenFileName)
|
||||
err := srv.initializeAuthToken()
|
||||
require.NoError(t, err)
|
||||
currentToken := srv.authToken
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go srv.refreshAuthTokenFromFileChanges(ctx, authTokenPath)
|
||||
go srv.refreshAuthTokenFromFileChanges(ctx, srv.authTokenPath)
|
||||
|
||||
// Wait for service to be ready.
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
|
||||
// Update the auth token file with a new secret.
|
||||
require.NoError(t, CreateAuthToken(walletDir, "localhost:7500"))
|
||||
require.NoError(t, CreateAuthToken(srv.authTokenPath, "localhost:7500"))
|
||||
|
||||
// The service should have picked up the file change and set the jwt secret to the new one.
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
newSecret := srv.jwtSecret
|
||||
require.Equal(t, true, len(newSecret) > 0)
|
||||
require.Equal(t, true, !bytes.Equal(currentSecret, newSecret))
|
||||
err = os.Remove(AuthTokenFileName)
|
||||
newToken := srv.authToken
|
||||
require.Equal(t, true, currentToken != newToken)
|
||||
err = os.Remove(srv.authTokenPath)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TODO: remove this test when legacy files are removed
|
||||
func TestServer_LegacyTokensStillWork(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
// Initializing for the first time, there is no auth token file in
|
||||
// the wallet directory, so we generate a jwt token and secret from scratch.
|
||||
walletDir := setupWalletDir(t)
|
||||
authTokenPath := filepath.Join(walletDir, api.AuthTokenFileName)
|
||||
|
||||
bytesBuf := new(bytes.Buffer)
|
||||
_, err := bytesBuf.WriteString("b5bbbaf533b625a93741978857f13d7adeca58445a1fb00ecf3373420b92776c")
|
||||
require.NoError(t, err)
|
||||
_, err = bytesBuf.WriteString("\n")
|
||||
require.NoError(t, err)
|
||||
_, err = bytesBuf.WriteString("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.MxwOozSH-TLbW_XKepjyYDHm2IT8Ki0tD3AHuajfNMg")
|
||||
require.NoError(t, err)
|
||||
_, err = bytesBuf.WriteString("\n")
|
||||
require.NoError(t, err)
|
||||
err = file.MkdirAll(walletDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = file.WriteFile(authTokenPath, bytesBuf.Bytes())
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := &Server{
|
||||
authTokenPath: authTokenPath,
|
||||
}
|
||||
|
||||
err = srv.initializeAuthToken()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, hexutil.Encode(srv.jwtSecret), "0xb5bbbaf533b625a93741978857f13d7adeca58445a1fb00ecf3373420b92776c")
|
||||
require.Equal(t, srv.authToken, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.MxwOozSH-TLbW_XKepjyYDHm2IT8Ki0tD3AHuajfNMg")
|
||||
|
||||
f, err := os.Open(filepath.Clean(srv.authTokenPath))
|
||||
require.NoError(t, err)
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
var lines []string
|
||||
|
||||
// Scan the file and collect lines, excluding empty lines
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.TrimSpace(line) != "" {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
require.Equal(t, len(lines), 2)
|
||||
require.LogsContain(t, hook, "Auth token does not follow our standards and should be regenerated")
|
||||
// Check for scanning errors
|
||||
err = scanner.Err()
|
||||
require.NoError(t, err)
|
||||
err = f.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = os.Remove(srv.authTokenPath)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TODO: remove this test when legacy files are removed
|
||||
func TestServer_LegacyTokensBadSecret(t *testing.T) {
|
||||
// Initializing for the first time, there is no auth token file in
|
||||
// the wallet directory, so we generate a jwt token and secret from scratch.
|
||||
walletDir := setupWalletDir(t)
|
||||
authTokenPath := filepath.Join(walletDir, api.AuthTokenFileName)
|
||||
|
||||
bytesBuf := new(bytes.Buffer)
|
||||
_, err := bytesBuf.WriteString("----------------")
|
||||
require.NoError(t, err)
|
||||
_, err = bytesBuf.WriteString("\n")
|
||||
require.NoError(t, err)
|
||||
_, err = bytesBuf.WriteString("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.MxwOozSH-TLbW_XKepjyYDHm2IT8Ki0tD3AHuajfNMg")
|
||||
require.NoError(t, err)
|
||||
_, err = bytesBuf.WriteString("\n")
|
||||
require.NoError(t, err)
|
||||
err = file.MkdirAll(walletDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = file.WriteFile(authTokenPath, bytesBuf.Bytes())
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := &Server{
|
||||
authTokenPath: authTokenPath,
|
||||
}
|
||||
|
||||
err = srv.initializeAuthToken()
|
||||
require.ErrorContains(t, "could not decode JWT secret", err)
|
||||
}
|
||||
|
||||
func Test_initializeAuthToken(t *testing.T) {
|
||||
// Initializing for the first time, there is no auth token file in
|
||||
// the wallet directory, so we generate a jwt token and secret from scratch.
|
||||
srv := &Server{}
|
||||
walletDir := setupWalletDir(t)
|
||||
token, err := srv.initializeAuthToken(walletDir)
|
||||
authTokenPath := filepath.Join(walletDir, api.AuthTokenFileName)
|
||||
srv := &Server{
|
||||
authTokenPath: authTokenPath,
|
||||
}
|
||||
err := srv.initializeAuthToken()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, len(srv.jwtSecret) > 0)
|
||||
|
||||
// Initializing second time, we generate something from the initial file.
|
||||
srv2 := &Server{}
|
||||
token2, err := srv2.initializeAuthToken(walletDir)
|
||||
srv2 := &Server{
|
||||
authTokenPath: authTokenPath,
|
||||
}
|
||||
err = srv2.initializeAuthToken()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, bytes.Equal(srv.jwtSecret, srv2.jwtSecret))
|
||||
require.Equal(t, token, token2)
|
||||
require.Equal(t, srv.authToken, srv2.authToken)
|
||||
|
||||
// Deleting the auth token and re-initializing means we create a jwt token
|
||||
// and secret from scratch again.
|
||||
srv3 := &Server{}
|
||||
walletDir = setupWalletDir(t)
|
||||
token3, err := srv3.initializeAuthToken(walletDir)
|
||||
authTokenPath = filepath.Join(walletDir, api.AuthTokenFileName)
|
||||
srv3 := &Server{
|
||||
authTokenPath: authTokenPath,
|
||||
}
|
||||
|
||||
err = srv3.initializeAuthToken()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, len(srv.jwtSecret) > 0)
|
||||
require.NotEqual(t, token, token3)
|
||||
require.NotEqual(t, srv.authToken, srv3.authToken)
|
||||
}
|
||||
|
||||
// "createTokenString" now uses jwt.RegisteredClaims instead of jwt.StandardClaims (deprecated),
|
||||
|
||||
@@ -2,7 +2,6 @@ package rpc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/io/file"
|
||||
@@ -20,8 +19,7 @@ func (s *Server) Initialize(w http.ResponseWriter, r *http.Request) {
|
||||
httputil.HandleError(w, errors.Wrap(err, "Could not check if wallet exists").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
authTokenPath := filepath.Join(s.walletDir, AuthTokenFileName)
|
||||
exists, err := file.Exists(authTokenPath, file.Regular)
|
||||
exists, err := file.Exists(s.authTokenPath, file.Regular)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Wrap(err, "Could not check if auth token exists").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/api"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/validator/accounts"
|
||||
"github.com/prysmaticlabs/prysm/v5/validator/keymanager"
|
||||
@@ -19,7 +20,7 @@ func TestInitialize(t *testing.T) {
|
||||
localWalletDir := setupWalletDir(t)
|
||||
|
||||
// Step 2: Optionally create a temporary 'auth-token' file
|
||||
authTokenPath := filepath.Join(localWalletDir, AuthTokenFileName)
|
||||
authTokenPath := filepath.Join(localWalletDir, api.AuthTokenFileName)
|
||||
_, err := os.Create(authTokenPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -34,7 +35,7 @@ func TestInitialize(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
_, err = acc.WalletCreate(context.Background())
|
||||
require.NoError(t, err)
|
||||
server := &Server{walletDir: localWalletDir}
|
||||
server := &Server{walletDir: localWalletDir, authTokenPath: authTokenPath}
|
||||
|
||||
// Step 4: Create an HTTP request and response recorder
|
||||
req := httptest.NewRequest(http.MethodGet, "/initialize", nil)
|
||||
@@ -43,6 +44,8 @@ func TestInitialize(t *testing.T) {
|
||||
// Step 5: Call the Initialize function
|
||||
server.Initialize(w, req)
|
||||
|
||||
require.Equal(t, w.Code, http.StatusOK)
|
||||
|
||||
// Step 6: Assert expectations
|
||||
result := w.Result()
|
||||
defer func() {
|
||||
|
||||
@@ -2,11 +2,9 @@ package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/prysmaticlabs/prysm/v5/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
@@ -15,8 +13,8 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// JWTInterceptor is a gRPC unary interceptor to authorize incoming requests.
|
||||
func (s *Server) JWTInterceptor() grpc.UnaryServerInterceptor {
|
||||
// AuthTokenInterceptor is a gRPC unary interceptor to authorize incoming requests.
|
||||
func (s *Server) AuthTokenInterceptor() grpc.UnaryServerInterceptor {
|
||||
return func(
|
||||
ctx context.Context,
|
||||
req interface{},
|
||||
@@ -35,8 +33,8 @@ func (s *Server) JWTInterceptor() grpc.UnaryServerInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
// JwtHttpInterceptor is an HTTP handler to authorize a route.
|
||||
func (s *Server) JwtHttpInterceptor(next http.Handler) http.Handler {
|
||||
// AuthTokenHandler is an HTTP handler to authorize a route.
|
||||
func (s *Server) AuthTokenHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// if it's not initialize or has a web prefix
|
||||
if strings.Contains(r.URL.Path, api.WebApiUrlPrefix) || strings.Contains(r.URL.Path, api.KeymanagerApiPrefix) {
|
||||
@@ -53,9 +51,8 @@ func (s *Server) JwtHttpInterceptor(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
token := tokenParts[1]
|
||||
_, err := jwt.Parse(token, s.validateJWT)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Errorf("forbidden: could not parse JWT token: %v", err).Error(), http.StatusForbidden)
|
||||
if strings.TrimSpace(token) != s.authToken || strings.TrimSpace(s.authToken) == "" {
|
||||
http.Error(w, "Forbidden: token value is invalid", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -78,16 +75,8 @@ func (s *Server) authorize(ctx context.Context) error {
|
||||
return status.Error(codes.Unauthenticated, "Invalid auth header, needs Bearer {token}")
|
||||
}
|
||||
token := strings.Split(authHeader[0], "Bearer ")[1]
|
||||
_, err := jwt.Parse(token, s.validateJWT)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Unauthenticated, "Could not parse JWT token: %v", err)
|
||||
if strings.TrimSpace(token) != s.authToken || strings.TrimSpace(s.authToken) == "" {
|
||||
return status.Errorf(codes.Unauthenticated, "Forbidden: token value is invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) validateJWT(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected JWT signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return s.jwtSecret, nil
|
||||
}
|
||||
|
||||
@@ -6,18 +6,18 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/prysmaticlabs/prysm/v5/api"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func TestServer_JWTInterceptor_Verify(t *testing.T) {
|
||||
func TestServer_AuthTokenInterceptor_Verify(t *testing.T) {
|
||||
token := "cool-token"
|
||||
s := Server{
|
||||
jwtSecret: []byte("testKey"),
|
||||
authToken: token,
|
||||
}
|
||||
interceptor := s.JWTInterceptor()
|
||||
interceptor := s.AuthTokenInterceptor()
|
||||
|
||||
unaryInfo := &grpc.UnaryServerInfo{
|
||||
FullMethod: "Proto.CreateWallet",
|
||||
@@ -25,22 +25,20 @@ func TestServer_JWTInterceptor_Verify(t *testing.T) {
|
||||
unaryHandler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
token, err := createTokenString(s.jwtSecret)
|
||||
require.NoError(t, err)
|
||||
ctxMD := map[string][]string{
|
||||
"authorization": {"Bearer " + token},
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = metadata.NewIncomingContext(ctx, ctxMD)
|
||||
_, err = interceptor(ctx, "xyz", unaryInfo, unaryHandler)
|
||||
_, err := interceptor(ctx, "xyz", unaryInfo, unaryHandler)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestServer_JWTInterceptor_BadToken(t *testing.T) {
|
||||
func TestServer_AuthTokenInterceptor_BadToken(t *testing.T) {
|
||||
s := Server{
|
||||
jwtSecret: []byte("testKey"),
|
||||
authToken: "cool-token",
|
||||
}
|
||||
interceptor := s.JWTInterceptor()
|
||||
interceptor := s.AuthTokenInterceptor()
|
||||
|
||||
unaryInfo := &grpc.UnaryServerInfo{
|
||||
FullMethod: "Proto.CreateWallet",
|
||||
@@ -49,111 +47,65 @@ func TestServer_JWTInterceptor_BadToken(t *testing.T) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
badServer := Server{
|
||||
jwtSecret: []byte("badTestKey"),
|
||||
}
|
||||
token, err := createTokenString(badServer.jwtSecret)
|
||||
require.NoError(t, err)
|
||||
ctxMD := map[string][]string{
|
||||
"authorization": {"Bearer " + token},
|
||||
"authorization": {"Bearer bad-token"},
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = metadata.NewIncomingContext(ctx, ctxMD)
|
||||
_, err = interceptor(ctx, "xyz", unaryInfo, unaryHandler)
|
||||
require.ErrorContains(t, "signature is invalid", err)
|
||||
_, err := interceptor(ctx, "xyz", unaryInfo, unaryHandler)
|
||||
require.ErrorContains(t, "token value is invalid", err)
|
||||
}
|
||||
|
||||
func TestServer_JWTInterceptor_InvalidSigningType(t *testing.T) {
|
||||
ss := &Server{jwtSecret: make([]byte, 32)}
|
||||
// Use a different signing type than the expected, HMAC.
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{})
|
||||
_, err := ss.validateJWT(token)
|
||||
require.ErrorContains(t, "unexpected JWT signing method", err)
|
||||
}
|
||||
func TestServer_AuthTokenHandler(t *testing.T) {
|
||||
token := "cool-token"
|
||||
|
||||
func TestServer_JwtHttpInterceptor(t *testing.T) {
|
||||
jwtKey, err := createRandomJWTSecret()
|
||||
require.NoError(t, err)
|
||||
|
||||
s := &Server{jwtSecret: jwtKey}
|
||||
testHandler := s.JwtHttpInterceptor(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
s := &Server{authToken: token}
|
||||
testHandler := s.AuthTokenHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Your test handler logic here
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := w.Write([]byte("Test Response"))
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
t.Run("no jwt was sent", func(t *testing.T) {
|
||||
t.Run("no auth token was sent", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, "/eth/v1/keystores", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, "/eth/v1/keystores", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
testHandler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
})
|
||||
t.Run("wrong jwt was sent", func(t *testing.T) {
|
||||
t.Run("wrong auth token was sent", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, "/eth/v1/keystores", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, "/eth/v1/keystores", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "Bearer YOUR_JWT_TOKEN") // Replace with a valid JWT token
|
||||
testHandler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusForbidden, rr.Code)
|
||||
})
|
||||
t.Run("jwt was sent", func(t *testing.T) {
|
||||
t.Run("good auth token was sent", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, "/eth/v1/keystores", nil)
|
||||
require.NoError(t, err)
|
||||
token, err := createTokenString(jwtKey)
|
||||
req, err := http.NewRequest(http.MethodGet, "/eth/v1/keystores", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "Bearer "+token) // Replace with a valid JWT token
|
||||
testHandler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
})
|
||||
t.Run("wrong jwt format was sent", func(t *testing.T) {
|
||||
t.Run("web endpoint needs auth token", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, "/eth/v1/keystores", nil)
|
||||
require.NoError(t, err)
|
||||
token, err := createTokenString(jwtKey)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "Bearer"+token) // no space was added // Replace with a valid JWT token
|
||||
testHandler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
})
|
||||
t.Run("wrong jwt no bearer format was sent", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, "/eth/v1/keystores", nil)
|
||||
require.NoError(t, err)
|
||||
token, err := createTokenString(jwtKey)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", token) // Replace with a valid JWT token
|
||||
testHandler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
})
|
||||
t.Run("broken jwt token format was sent", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, "/eth/v1/keystores", nil)
|
||||
require.NoError(t, err)
|
||||
token, err := createTokenString(jwtKey)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", "Bearer "+token[0:2]+" "+token[2:]) // Replace with a valid JWT token
|
||||
testHandler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusForbidden, rr.Code)
|
||||
})
|
||||
t.Run("web endpoint needs jwt token", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, "/api/v2/validator/beacon/status", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, "/api/v2/validator/beacon/status", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
testHandler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
})
|
||||
t.Run("initialize does not need jwt", func(t *testing.T) {
|
||||
t.Run("initialize does not need auth", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, api.WebUrlPrefix+"initialize", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, api.WebUrlPrefix+"initialize", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
testHandler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
})
|
||||
t.Run("health does not need jwt", func(t *testing.T) {
|
||||
t.Run("health does not need auth", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(http.MethodGet, api.WebUrlPrefix+"health/logs", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, api.WebUrlPrefix+"health/logs", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
testHandler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
@@ -47,6 +47,7 @@ type Config struct {
|
||||
CertFlag string
|
||||
KeyFlag string
|
||||
ValDB db.Database
|
||||
AuthTokenPath string
|
||||
WalletDir string
|
||||
ValidatorService *client.ValidatorService
|
||||
SyncChecker client.SyncChecker
|
||||
@@ -87,6 +88,8 @@ type Server struct {
|
||||
validatorService *client.ValidatorService
|
||||
syncChecker client.SyncChecker
|
||||
genesisFetcher client.GenesisFetcher
|
||||
authTokenPath string
|
||||
authToken string
|
||||
walletDir string
|
||||
wallet *wallet.Wallet
|
||||
walletInitializedFeed *event.Feed
|
||||
@@ -123,6 +126,7 @@ func NewServer(ctx context.Context, cfg *Config) *Server {
|
||||
validatorService: cfg.ValidatorService,
|
||||
syncChecker: cfg.SyncChecker,
|
||||
genesisFetcher: cfg.GenesisFetcher,
|
||||
authTokenPath: cfg.AuthTokenPath,
|
||||
walletDir: cfg.WalletDir,
|
||||
walletInitializedFeed: cfg.WalletInitializedFeed,
|
||||
walletInitialized: cfg.Wallet != nil,
|
||||
@@ -136,6 +140,19 @@ func NewServer(ctx context.Context, cfg *Config) *Server {
|
||||
beaconApiEndpoint: cfg.BeaconApiEndpoint,
|
||||
router: cfg.Router,
|
||||
}
|
||||
|
||||
if server.authTokenPath == "" && server.walletDir != "" {
|
||||
server.authTokenPath = filepath.Join(server.walletDir, api.AuthTokenFileName)
|
||||
}
|
||||
|
||||
if server.authTokenPath != "" {
|
||||
if err := server.initializeAuthToken(); err != nil {
|
||||
log.WithError(err).Error("Could not initialize web auth token")
|
||||
}
|
||||
validatorWebAddr := fmt.Sprintf("%s:%d", server.validatorGatewayHost, server.validatorGatewayPort)
|
||||
logValidatorWebAuth(validatorWebAddr, server.authToken, server.authTokenPath)
|
||||
go server.refreshAuthTokenFromFileChanges(server.ctx, server.authTokenPath)
|
||||
}
|
||||
// immediately register routes to override any catchalls
|
||||
if err := server.InitializeRoutes(); err != nil {
|
||||
log.WithError(err).Fatal("Could not initialize routes")
|
||||
@@ -146,7 +163,7 @@ func NewServer(ctx context.Context, cfg *Config) *Server {
|
||||
// Start the gRPC server.
|
||||
func (s *Server) Start() {
|
||||
// Setup the gRPC server options and TLS configuration.
|
||||
address := fmt.Sprintf("%s:%s", s.host, s.port)
|
||||
address := net.JoinHostPort(s.host, s.port)
|
||||
lis, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Could not listen to port in Start() %s", address)
|
||||
@@ -163,7 +180,7 @@ func (s *Server) Start() {
|
||||
),
|
||||
grpcprometheus.UnaryServerInterceptor,
|
||||
grpcopentracing.UnaryServerInterceptor(),
|
||||
s.JWTInterceptor(),
|
||||
s.AuthTokenInterceptor(),
|
||||
)),
|
||||
}
|
||||
grpcprometheus.EnableHandlingTimeHistogram()
|
||||
@@ -198,17 +215,6 @@ func (s *Server) Start() {
|
||||
}()
|
||||
|
||||
log.WithField("address", address).Info("gRPC server listening on address")
|
||||
if s.walletDir != "" {
|
||||
token, err := s.initializeAuthToken(s.walletDir)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not initialize web auth token")
|
||||
return
|
||||
}
|
||||
validatorWebAddr := fmt.Sprintf("%s:%d", s.validatorGatewayHost, s.validatorGatewayPort)
|
||||
authTokenPath := filepath.Join(s.walletDir, AuthTokenFileName)
|
||||
logValidatorWebAuth(validatorWebAddr, token, authTokenPath)
|
||||
go s.refreshAuthTokenFromFileChanges(s.ctx, authTokenPath)
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeRoutes initializes pure HTTP REST endpoints for the validator client.
|
||||
@@ -218,7 +224,7 @@ func (s *Server) InitializeRoutes() error {
|
||||
return errors.New("no router found on server")
|
||||
}
|
||||
// Adding Auth Interceptor for the routes below
|
||||
s.router.Use(s.JwtHttpInterceptor)
|
||||
s.router.Use(s.AuthTokenHandler)
|
||||
// Register all services, HandleFunc calls, etc.
|
||||
// ...
|
||||
s.router.HandleFunc("/eth/v1/keystores", s.ListKeystores).Methods(http.MethodGet)
|
||||
|
||||
Reference in New Issue
Block a user