github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/credentials/static.go (about)

     1  package credentials
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/golang-jwt/jwt/v4"
    10  	"github.com/ydb-platform/ydb-go-genproto/Ydb_Auth_V1"
    11  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb"
    12  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Auth"
    13  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Operations"
    14  	"google.golang.org/grpc"
    15  
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/secret"
    17  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
    18  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    19  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring"
    20  )
    21  
    22  const TokenRefreshDivisor = 10
    23  
    24  var (
    25  	_ Credentials             = (*Static)(nil)
    26  	_ fmt.Stringer            = (*Static)(nil)
    27  	_ StaticCredentialsOption = grpcDialOptionsOption(nil)
    28  )
    29  
    30  type grpcDialOptionsOption []grpc.DialOption
    31  
    32  func (opts grpcDialOptionsOption) ApplyStaticCredentialsOption(c *Static) {
    33  	c.opts = opts
    34  }
    35  
    36  type StaticCredentialsOption interface {
    37  	ApplyStaticCredentialsOption(c *Static)
    38  }
    39  
    40  func WithGrpcDialOptions(opts ...grpc.DialOption) grpcDialOptionsOption {
    41  	return opts
    42  }
    43  
    44  func NewStaticCredentials(user, password, endpoint string, opts ...StaticCredentialsOption) *Static {
    45  	c := &Static{
    46  		user:       user,
    47  		password:   password,
    48  		endpoint:   endpoint,
    49  		sourceInfo: stack.Record(1),
    50  	}
    51  	for _, opt := range opts {
    52  		if opt != nil {
    53  			opt.ApplyStaticCredentialsOption(c)
    54  		}
    55  	}
    56  
    57  	return c
    58  }
    59  
    60  var (
    61  	_ Credentials  = (*Static)(nil)
    62  	_ fmt.Stringer = (*Static)(nil)
    63  )
    64  
    65  // Static implements Credentials interface with static
    66  // authorization parameters.
    67  type Static struct {
    68  	user       string
    69  	password   string
    70  	endpoint   string
    71  	opts       []grpc.DialOption
    72  	token      string
    73  	requestAt  time.Time
    74  	mu         sync.Mutex
    75  	sourceInfo string
    76  }
    77  
    78  //nolint:funlen
    79  func (c *Static) Token(ctx context.Context) (token string, err error) {
    80  	c.mu.Lock()
    81  	defer c.mu.Unlock()
    82  	if time.Until(c.requestAt) > 0 {
    83  		return c.token, nil
    84  	}
    85  	cc, err := grpc.DialContext(ctx, c.endpoint, c.opts...) //nolint:staticcheck,nolintlint
    86  	if err != nil {
    87  		return "", xerrors.WithStackTrace(
    88  			fmt.Errorf("dial failed: %w", err),
    89  		)
    90  	}
    91  	defer cc.Close()
    92  
    93  	client := Ydb_Auth_V1.NewAuthServiceClient(cc)
    94  
    95  	response, err := client.Login(ctx, &Ydb_Auth.LoginRequest{
    96  		OperationParams: &Ydb_Operations.OperationParams{
    97  			OperationMode:    0,
    98  			OperationTimeout: nil,
    99  			CancelAfter:      nil,
   100  			Labels:           nil,
   101  			ReportCostInfo:   0,
   102  		},
   103  		User:     c.user,
   104  		Password: c.password,
   105  	})
   106  	if err != nil {
   107  		return "", xerrors.WithStackTrace(err)
   108  	}
   109  
   110  	switch {
   111  	case !response.GetOperation().GetReady():
   112  		return "", xerrors.WithStackTrace(
   113  			fmt.Errorf("operation '%s' not ready: %v",
   114  				response.GetOperation().GetId(),
   115  				response.GetOperation().GetIssues(),
   116  			),
   117  		)
   118  
   119  	case response.GetOperation().GetStatus() != Ydb.StatusIds_SUCCESS:
   120  		return "", xerrors.WithStackTrace(
   121  			xerrors.Operation(
   122  				xerrors.FromOperation(response.GetOperation()),
   123  				xerrors.WithAddress(c.endpoint),
   124  			),
   125  		)
   126  	}
   127  	var result Ydb_Auth.LoginResult
   128  	if err = response.GetOperation().GetResult().UnmarshalTo(&result); err != nil {
   129  		return "", xerrors.WithStackTrace(err)
   130  	}
   131  
   132  	expiresAt, err := parseExpiresAt(result.GetToken())
   133  	if err != nil {
   134  		return "", xerrors.WithStackTrace(err)
   135  	}
   136  
   137  	c.requestAt = time.Now().Add(time.Until(expiresAt) / TokenRefreshDivisor)
   138  	c.token = result.GetToken()
   139  
   140  	return c.token, nil
   141  }
   142  
   143  func parseExpiresAt(raw string) (expiresAt time.Time, err error) {
   144  	var claims jwt.RegisteredClaims
   145  	if _, _, err = jwt.NewParser().ParseUnverified(raw, &claims); err != nil {
   146  		return expiresAt, xerrors.WithStackTrace(err)
   147  	}
   148  
   149  	return claims.ExpiresAt.Time, nil
   150  }
   151  
   152  func (c *Static) String() string {
   153  	buffer := xstring.Buffer()
   154  	defer buffer.Free()
   155  	buffer.WriteString("Static{User:")
   156  	fmt.Fprintf(buffer, "%q", c.user)
   157  	buffer.WriteString(",Password:")
   158  	fmt.Fprintf(buffer, "%q", secret.Password(c.password))
   159  	buffer.WriteString(",Token:")
   160  	fmt.Fprintf(buffer, "%q", secret.Token(c.token))
   161  	if c.sourceInfo != "" {
   162  		buffer.WriteString(",From:")
   163  		fmt.Fprintf(buffer, "%q", c.sourceInfo)
   164  	}
   165  	buffer.WriteByte('}')
   166  
   167  	return buffer.String()
   168  }