mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-25 21:07:18 +00:00
95 lines
3.8 KiB
Go
95 lines
3.8 KiB
Go
|
package rpc
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"time"
|
||
|
|
||
|
"github.com/dgrijalva/jwt-go"
|
||
|
"github.com/pkg/errors"
|
||
|
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
|
||
|
"github.com/prysmaticlabs/prysm/shared/promptutil"
|
||
|
"github.com/prysmaticlabs/prysm/shared/roughtime"
|
||
|
"golang.org/x/crypto/bcrypt"
|
||
|
"google.golang.org/grpc/codes"
|
||
|
"google.golang.org/grpc/status"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
tokenExpiryLength = 20 * time.Minute
|
||
|
hashCost = 8
|
||
|
)
|
||
|
|
||
|
// Signup to authenticate access to the validator RPC API using bcrypt and
|
||
|
// a sufficiently strong password check.
|
||
|
func (s *Server) Signup(ctx context.Context, req *pb.AuthRequest) (*pb.AuthResponse, error) {
|
||
|
// First, we check if the validator already has a password. In this case,
|
||
|
// the user should NOT be able to signup and the function will return an error.
|
||
|
existingPassword, err := s.valDB.HashedPasswordForAPI(ctx)
|
||
|
if err != nil {
|
||
|
return nil, status.Error(codes.Internal, "Could not retrieve hashed password from database")
|
||
|
}
|
||
|
if len(existingPassword) != 0 {
|
||
|
return nil, status.Error(codes.PermissionDenied, "Validator already has a password set, cannot signup")
|
||
|
}
|
||
|
// We check the strength of the password to ensure it is high-entropy,
|
||
|
// has the required character count, and contains only unicode characters.
|
||
|
if err := promptutil.ValidatePasswordInput(req.Password); err != nil {
|
||
|
return nil, status.Error(codes.InvalidArgument, "Could not validate password input")
|
||
|
}
|
||
|
// Salt and hash the password using the bcrypt algorithm
|
||
|
// The second argument is the cost of hashing, which we arbitrarily set as 8
|
||
|
// (this value can be more or less, depending on the computing power you wish to utilize)
|
||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), hashCost)
|
||
|
if err != nil {
|
||
|
return nil, status.Error(codes.Internal, "Could not generate hashed password")
|
||
|
}
|
||
|
// We store the hashed password to disk.
|
||
|
if err := s.valDB.SaveHashedPasswordForAPI(ctx, hashedPassword); err != nil {
|
||
|
return nil, status.Error(codes.Internal, "Could not save hashed password to database")
|
||
|
}
|
||
|
return s.sendAuthResponse()
|
||
|
}
|
||
|
|
||
|
// Login to authenticate with the validator RPC API using a password.
|
||
|
func (s *Server) Login(ctx context.Context, req *pb.AuthRequest) (*pb.AuthResponse, error) {
|
||
|
// We retrieve the hashed password for the validator API from disk.
|
||
|
hashedPassword, err := s.valDB.HashedPasswordForAPI(ctx)
|
||
|
if err != nil {
|
||
|
return nil, status.Error(codes.Internal, "Could not retrieve hashed password from database")
|
||
|
}
|
||
|
// Compare the stored hashed password, with the hashed version of the password that was received.
|
||
|
if err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(req.Password)); err != nil {
|
||
|
return nil, status.Error(codes.Unauthenticated, "Incorrect password")
|
||
|
}
|
||
|
return s.sendAuthResponse()
|
||
|
}
|
||
|
|
||
|
// Sends an auth response via gRPC containing a new JWT token.
|
||
|
func (s *Server) sendAuthResponse() (*pb.AuthResponse, error) {
|
||
|
// If everything is fine here, construct the auth token.
|
||
|
tokenString, expirationTime, err := s.createTokenString()
|
||
|
if err != nil {
|
||
|
return nil, status.Error(codes.Internal, "Could not create jwt token string")
|
||
|
}
|
||
|
return &pb.AuthResponse{
|
||
|
Token: []byte(tokenString),
|
||
|
TokenExpiration: expirationTime,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// Creates a JWT token string using the JWT key with an expiration timestamp.
|
||
|
func (s *Server) createTokenString() (string, uint64, error) {
|
||
|
expirationTime := roughtime.Now().Add(tokenExpiryLength)
|
||
|
claims := &jwt.StandardClaims{
|
||
|
// In JWT, the expiry time is expressed as unix milliseconds.
|
||
|
ExpiresAt: expirationTime.Unix(),
|
||
|
}
|
||
|
// Declare the token with the algorithm used for signing, and the claims.
|
||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||
|
tokenString, err := token.SignedString(s.jwtKey)
|
||
|
if err != nil {
|
||
|
return "", 0, errors.Wrap(err, "could not sign token")
|
||
|
}
|
||
|
return tokenString, uint64(claims.ExpiresAt), nil
|
||
|
}
|