diff --git a/cmd/flipt/main.go b/cmd/flipt/main.go index f7fa6ac6e6..c6e1b02274 100644 --- a/cmd/flipt/main.go +++ b/cmd/flipt/main.go @@ -34,16 +34,19 @@ import ( "go.flipt.io/flipt/internal/config" "go.flipt.io/flipt/internal/info" "go.flipt.io/flipt/internal/server" + authtoken "go.flipt.io/flipt/internal/server/auth/method/token" "go.flipt.io/flipt/internal/server/cache" "go.flipt.io/flipt/internal/server/cache/memory" "go.flipt.io/flipt/internal/server/cache/redis" "go.flipt.io/flipt/internal/storage" + authsql "go.flipt.io/flipt/internal/storage/auth/sql" "go.flipt.io/flipt/internal/storage/sql" "go.flipt.io/flipt/internal/storage/sql/mysql" "go.flipt.io/flipt/internal/storage/sql/postgres" "go.flipt.io/flipt/internal/storage/sql/sqlite" "go.flipt.io/flipt/internal/telemetry" pb "go.flipt.io/flipt/rpc/flipt" + authrpc "go.flipt.io/flipt/rpc/flipt/auth" "go.flipt.io/flipt/swagger" "go.flipt.io/flipt/ui" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" @@ -542,6 +545,16 @@ func run(ctx context.Context, logger *zap.Logger) error { }) pb.RegisterFliptServer(grpcServer, srv) + + // register auth service + if cfg.Authentication.Enabled { + store := authsql.NewStore(driver, sql.BuilderFor(db, driver), logger) + tokenServer := authtoken.NewServer(logger, store) + + authrpc.RegisterAuthenticationMethodTokenServiceServer(grpcServer, tokenServer) + logger.Info("authentication server registered") + } + grpc_prometheus.EnableHandlingTimeHistogram() grpc_prometheus.Register(grpcServer) reflection.Register(grpcServer) @@ -606,6 +619,12 @@ func run(ctx context.Context, logger *zap.Logger) error { return fmt.Errorf("registering grpc gateway: %w", err) } + if cfg.Authentication.Enabled { + if err := authrpc.RegisterAuthenticationMethodTokenServiceHandler(ctx, api, conn); err != nil { + return fmt.Errorf("registering auth grpc gateway: %w", err) + } + } + if cfg.Cors.Enabled { cors := cors.New(cors.Options{ AllowedOrigins: cfg.Cors.AllowedOrigins, @@ -637,6 +656,7 @@ func run(ctx context.Context, logger *zap.Logger) error { r.Use(middleware.Recoverer) r.Mount("/metrics", promhttp.Handler()) r.Mount("/api/v1", api) + r.Mount("/auth/v1", api) r.Mount("/debug", middleware.Profiler()) r.Route("/meta", func(r chi.Router) { diff --git a/go.mod b/go.mod index b5f15717c9..c572287413 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/go-sql-driver/mysql v1.6.0 github.com/gofrs/uuid v4.3.0+incompatible github.com/golang-migrate/migrate/v4 v4.15.2 + github.com/google/go-cmp v0.5.9 github.com/google/go-github/v32 v32.1.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 diff --git a/go.sum b/go.sum index ea64808729..6461ad6513 100644 --- a/go.sum +++ b/go.sum @@ -637,6 +637,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= diff --git a/internal/config/authentication.go b/internal/config/authentication.go new file mode 100644 index 0000000000..329e39b67b --- /dev/null +++ b/internal/config/authentication.go @@ -0,0 +1,18 @@ +package config + +import "github.com/spf13/viper" + +var _ defaulter = (*AuthenticationConfig)(nil) + +// AuthenticationConfig configures Flipts authentication mechanisms +type AuthenticationConfig struct { + Enabled bool `json:"enabled,omitempty" mapstructure:"enabled"` +} + +func (a *AuthenticationConfig) setDefaults(v *viper.Viper) []string { + v.SetDefault("authentication", map[string]any{ + "enabled": false, + }) + + return nil +} diff --git a/internal/config/config.go b/internal/config/config.go index f6800766e1..1333704eec 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -35,15 +35,16 @@ var decodeHooks = mapstructure.ComposeDecodeHookFunc( // then this will be called after unmarshalling, such that the function can emit // any errors derived from the resulting state of the configuration. type Config struct { - Log LogConfig `json:"log,omitempty" mapstructure:"log"` - UI UIConfig `json:"ui,omitempty" mapstructure:"ui"` - Cors CorsConfig `json:"cors,omitempty" mapstructure:"cors"` - Cache CacheConfig `json:"cache,omitempty" mapstructure:"cache"` - Server ServerConfig `json:"server,omitempty" mapstructure:"server"` - Tracing TracingConfig `json:"tracing,omitempty" mapstructure:"tracing"` - Database DatabaseConfig `json:"database,omitempty" mapstructure:"db"` - Meta MetaConfig `json:"meta,omitempty" mapstructure:"meta"` - Warnings []string `json:"warnings,omitempty"` + Log LogConfig `json:"log,omitempty" mapstructure:"log"` + UI UIConfig `json:"ui,omitempty" mapstructure:"ui"` + Cors CorsConfig `json:"cors,omitempty" mapstructure:"cors"` + Cache CacheConfig `json:"cache,omitempty" mapstructure:"cache"` + Server ServerConfig `json:"server,omitempty" mapstructure:"server"` + Tracing TracingConfig `json:"tracing,omitempty" mapstructure:"tracing"` + Database DatabaseConfig `json:"database,omitempty" mapstructure:"db"` + Meta MetaConfig `json:"meta,omitempty" mapstructure:"meta"` + Authentication AuthenticationConfig `json:"authentication,omitempty" mapstructure:"authentication"` + Warnings []string `json:"warnings,omitempty"` } func Load(path string) (*Config, error) { diff --git a/internal/server/auth/method/token/server.go b/internal/server/auth/method/token/server.go new file mode 100644 index 0000000000..a7c92d3bc4 --- /dev/null +++ b/internal/server/auth/method/token/server.go @@ -0,0 +1,57 @@ +package token + +import ( + "context" + "fmt" + + "go.flipt.io/flipt/internal/storage" + "go.flipt.io/flipt/rpc/flipt/auth" + "go.uber.org/zap" +) + +const ( + storageMetadataNameKey = "io.flipt.auth.token.name" + storageMetadataDescriptionKey = "io.flipt.auth.token.description" +) + +// Server is an implementation of auth.AuthenticationMethodTokenServiceServer +// +// It is used to create static tokens within the backing AuthenticationStore. +type Server struct { + logger *zap.Logger + store storage.AuthenticationStore + auth.UnimplementedAuthenticationMethodTokenServiceServer +} + +// NewServer constructs and configures a new *Server. +func NewServer(logger *zap.Logger, store storage.AuthenticationStore) *Server { + return &Server{ + logger: logger, + store: store, + } +} + +// CreateToken adapts and delegates the token request to the backing AuthenticationStore. +// +// Implicitly, the Authentication created will be of type auth.Method_TOKEN. +// Name and Description are both stored in Authentication.Metadata. +// Given the token is created successfully, the generate clientToken string is returned. +// Along with the created Authentication, which includes it's identifier and associated timestamps. +func (s *Server) CreateToken(ctx context.Context, req *auth.CreateTokenRequest) (*auth.CreateTokenResponse, error) { + clientToken, authentication, err := s.store.CreateAuthentication(ctx, &storage.CreateAuthenticationRequest{ + Method: auth.Method_TOKEN, + ExpiresAt: req.ExpiresAt, + Metadata: map[string]string{ + storageMetadataNameKey: req.GetName(), + storageMetadataDescriptionKey: req.GetDescription(), + }, + }) + if err != nil { + return nil, fmt.Errorf("attempting to create token: %w", err) + } + + return &auth.CreateTokenResponse{ + ClientToken: clientToken, + Authentication: authentication, + }, nil +} diff --git a/internal/server/auth/method/token/server_test.go b/internal/server/auth/method/token/server_test.go new file mode 100644 index 0000000000..dd261ea341 --- /dev/null +++ b/internal/server/auth/method/token/server_test.go @@ -0,0 +1,98 @@ +package token + +import ( + "context" + "net" + "testing" + + "github.com/google/go-cmp/cmp" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.flipt.io/flipt/internal/server" + "go.flipt.io/flipt/internal/storage/auth/memory" + "go.flipt.io/flipt/rpc/flipt/auth" + "go.uber.org/zap/zaptest" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestServer(t *testing.T) { + var ( + logger = zaptest.NewLogger(t) + store = memory.NewStore() + listener = bufconn.Listen(1024 * 1024) + server = grpc.NewServer( + grpc_middleware.WithUnaryServerChain( + server.ErrorUnaryInterceptor, + ), + ) + errC = make(chan error) + shutdown = func(t *testing.T) { + t.Helper() + + server.Stop() + if err := <-errC; err != nil { + t.Fatal(err) + } + } + ) + + defer shutdown(t) + + auth.RegisterAuthenticationMethodTokenServiceServer(server, NewServer(logger, store)) + + go func() { + errC <- server.Serve(listener) + }() + + var ( + ctx = context.Background() + dialer = func(context.Context, string) (net.Conn, error) { + return listener.Dial() + } + ) + + conn, err := grpc.DialContext(ctx, "", grpc.WithInsecure(), grpc.WithContextDialer(dialer)) + require.NoError(t, err) + defer conn.Close() + + client := auth.NewAuthenticationMethodTokenServiceClient(conn) + + // attempt to create token + resp, err := client.CreateToken(ctx, &auth.CreateTokenRequest{ + Name: "access_all_areas", + Description: "Super secret skeleton key", + }) + require.NoError(t, err) + + // assert auth is as expected + metadata := resp.Authentication.Metadata + assert.Equal(t, "access_all_areas", metadata["io.flipt.auth.token.name"]) + assert.Equal(t, "Super secret skeleton key", metadata["io.flipt.auth.token.description"]) + + // ensure client token can be used on store to fetch authentication + // and that the authentication returned matches the one received + // by the client + retrieved, err := store.GetAuthenticationByClientToken(ctx, resp.ClientToken) + require.NoError(t, err) + + // switch to go-cmp here to do the comparisons since assert trips up + // on the unexported sizeCache values. + if diff := cmp.Diff(retrieved, resp.Authentication, protocmp.Transform()); err != nil { + t.Errorf("-exp/+got:\n%s", diff) + } + + // attempt to create token with invalid expires at + _, err = client.CreateToken(ctx, &auth.CreateTokenRequest{ + Name: "access_all_areas", + Description: "Super secret skeleton key", + // invalid expires at, nanos must be positive + ExpiresAt: ×tamppb.Timestamp{Nanos: -1}, + }) + require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "attempting to create token: invalid expiry time: nanos:-1")) +} diff --git a/internal/storage/auth/auth.go b/internal/storage/auth/auth.go new file mode 100644 index 0000000000..c073ab9f8f --- /dev/null +++ b/internal/storage/auth/auth.go @@ -0,0 +1,49 @@ +package auth + +import ( + "bytes" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "fmt" +) + +const decodedTokenLen = 32 + +// GenerateRandomToken produces a URL safe base64 encoded string of random characters +// the data is sourced from a pseudo-random input stream +func GenerateRandomToken() string { + var token [decodedTokenLen]byte + if _, err := rand.Read(token[:]); err != nil { + panic(err) + } + + return base64.URLEncoding.EncodeToString(token[:]) +} + +// HashClientToken performs a SHA256 sum on the input string +// it returns the result as a URL safe base64 encoded string +func HashClientToken(token string) (string, error) { + // produce SHA256 hash of token + hash := sha256.New() + if _, err := hash.Write([]byte(token)); err != nil { + return "", fmt.Errorf("hashing client token: %w", err) + } + + // base64(sha256sum) + var ( + data = make([]byte, 0, base64.URLEncoding.EncodedLen(hash.Size())) + buf = bytes.NewBuffer(data) + enc = base64.NewEncoder(base64.URLEncoding, buf) + ) + + if _, err := enc.Write(hash.Sum(nil)); err != nil { + return "", fmt.Errorf("hashing client token: %w", err) + } + + if err := enc.Close(); err != nil { + return "", fmt.Errorf("hashing client token: %w", err) + } + + return buf.String(), nil +} diff --git a/internal/storage/auth/auth_test.go b/internal/storage/auth/auth_test.go new file mode 100644 index 0000000000..de9a147639 --- /dev/null +++ b/internal/storage/auth/auth_test.go @@ -0,0 +1,29 @@ +package auth + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" +) + +func FuzzHashClientToken(f *testing.F) { + for _, seed := range []string{ + "hello, world", + "supersecretstring", + "egGpvIxtdG6tI3OIJjXOrv7xZW3hRMYg/Lt/G6X/UEwC", + } { + f.Add(seed) + } + for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} { + f.Add(string(seed)) + } + f.Fuzz(func(t *testing.T, token string) { + hashed, err := HashClientToken(token) + require.NoError(t, err) + require.NotEmpty(t, hashed, "hashed result is empty") + + _, err = base64.URLEncoding.DecodeString(hashed) + require.NoError(t, err) + }) +} diff --git a/internal/storage/auth/memory/store.go b/internal/storage/auth/memory/store.go new file mode 100644 index 0000000000..5214b0817f --- /dev/null +++ b/internal/storage/auth/memory/store.go @@ -0,0 +1,127 @@ +package memory + +import ( + "context" + "fmt" + "sync" + + "github.com/gofrs/uuid" + "go.flipt.io/flipt/errors" + "go.flipt.io/flipt/internal/storage" + "go.flipt.io/flipt/internal/storage/auth" + rpcauth "go.flipt.io/flipt/rpc/flipt/auth" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// Store is an in-memory implementation of storage.AuthenticationStore +// +// Authentications are stored in a map by hashedClientToken. +// Access to the map is protected by a mutex, meaning this is implementation +// is safe to use concurrently. +type Store struct { + mu sync.Mutex + auths map[string]*rpcauth.Authentication + + now func() *timestamppb.Timestamp + generateID func() string + generateToken func() string +} + +// Option is a type which configures a *Store +type Option func(*Store) + +// NewStore instantiates a new in-memory implementation of storage.AuthenticationStore +func NewStore(opts ...Option) *Store { + store := &Store{ + auths: map[string]*rpcauth.Authentication{}, + now: timestamppb.Now, + generateID: func() string { + return uuid.Must(uuid.NewV4()).String() + }, + generateToken: auth.GenerateRandomToken, + } + + for _, opt := range opts { + opt(store) + } + + return store +} + +// WithNowFunc overrides the stores now() function used to obtain +// a protobuf timestamp representative of the current time of evaluation. +func WithNowFunc(fn func() *timestamppb.Timestamp) Option { + return func(s *Store) { + s.now = fn + } +} + +// WithTokenGeneratorFunc overrides the stores token generator function +// used to generate new random token strings as client tokens, when +// creating new instances of Authentication. +// The default is a pseudo-random string of bytes base64 encoded. +func WithTokenGeneratorFunc(fn func() string) Option { + return func(s *Store) { + s.generateToken = fn + } +} + +// WithIDGeneratorFunc overrides the stores ID generator function +// used to generate new random ID strings, when creating new instances +// of Authentications. +// The default is a string containing a valid UUID (V4). +func WithIDGeneratorFunc(fn func() string) Option { + return func(s *Store) { + s.generateID = fn + } +} + +// CreateAuthentication creates a new instance of an Authentication and returns a unique clientToken +// string which can be used to retrieve the Authentication again via GetAuthenticationByClientToken. +func (s *Store) CreateAuthentication(_ context.Context, r *storage.CreateAuthenticationRequest) (string, *rpcauth.Authentication, error) { + if r.ExpiresAt != nil && !r.ExpiresAt.IsValid() { + return "", nil, errors.ErrInvalidf("invalid expiry time: %v", r.ExpiresAt) + } + + var ( + now = s.now() + clientToken = s.generateToken() + authentication = &rpcauth.Authentication{ + Id: s.generateID(), + Method: r.Method, + Metadata: r.Metadata, + ExpiresAt: r.ExpiresAt, + CreatedAt: now, + UpdatedAt: now, + } + ) + + hashedToken, err := auth.HashClientToken(clientToken) + if err != nil { + return "", nil, fmt.Errorf("creating authentication: %w", err) + } + + s.mu.Lock() + s.auths[hashedToken] = authentication + s.mu.Unlock() + + return clientToken, authentication, nil +} + +// GetAuthenticationByClientToken retrieves an instance of Authentication from the backing +// store using the provided clientToken string as the key. +func (s *Store) GetAuthenticationByClientToken(ctx context.Context, clientToken string) (*rpcauth.Authentication, error) { + hashedToken, err := auth.HashClientToken(clientToken) + if err != nil { + return nil, fmt.Errorf("getting authentication by token: %w", err) + } + + s.mu.Lock() + authentication, ok := s.auths[hashedToken] + s.mu.Unlock() + if !ok { + return nil, errors.ErrNotFoundf("getting authentication by token") + } + + return authentication, nil +} diff --git a/internal/storage/sql/auth/store.go b/internal/storage/auth/sql/store.go similarity index 72% rename from internal/storage/sql/auth/store.go rename to internal/storage/auth/sql/store.go index acf44c585c..eecf4a0290 100644 --- a/internal/storage/sql/auth/store.go +++ b/internal/storage/auth/sql/store.go @@ -1,19 +1,16 @@ -package auth +package sql import ( - "bytes" "context" - "crypto/rand" - "crypto/sha256" - "encoding/base64" "fmt" sq "github.com/Masterminds/squirrel" "github.com/gofrs/uuid" "go.flipt.io/flipt/internal/storage" + "go.flipt.io/flipt/internal/storage/auth" fliptsql "go.flipt.io/flipt/internal/storage/sql" - "go.flipt.io/flipt/rpc/flipt/auth" + rpcauth "go.flipt.io/flipt/rpc/flipt/auth" "go.uber.org/zap" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -45,7 +42,7 @@ func NewStore(driver fliptsql.Driver, builder sq.StatementBuilderType, logger *z generateID: func() string { return uuid.Must(uuid.NewV4()).String() }, - generateToken: generateRandomToken, + generateToken: auth.GenerateRandomToken, } for _, opt := range opts { @@ -84,11 +81,11 @@ func WithIDGeneratorFunc(fn func() string) Option { } // CreateAuthentication creates and persists an instance of an Authentication. -func (s *Store) CreateAuthentication(ctx context.Context, r *storage.CreateAuthenticationRequest) (string, *auth.Authentication, error) { +func (s *Store) CreateAuthentication(ctx context.Context, r *storage.CreateAuthenticationRequest) (string, *rpcauth.Authentication, error) { var ( now = s.now() clientToken = s.generateToken() - authentication = auth.Authentication{ + authentication = rpcauth.Authentication{ Id: s.generateID(), Method: r.Method, Metadata: r.Metadata, @@ -98,7 +95,7 @@ func (s *Store) CreateAuthentication(ctx context.Context, r *storage.CreateAuthe } ) - hashedToken, err := hashClientToken(clientToken) + hashedToken, err := auth.HashClientToken(clientToken) if err != nil { return "", nil, fmt.Errorf("creating authentication: %w", err) } @@ -118,7 +115,7 @@ func (s *Store) CreateAuthentication(ctx context.Context, r *storage.CreateAuthe &hashedToken, &authentication.Method, &fliptsql.JSONField[map[string]string]{T: authentication.Metadata}, - &fliptsql.Timestamp{Timestamp: authentication.ExpiresAt}, + &fliptsql.NullableTimestamp{Timestamp: authentication.ExpiresAt}, &fliptsql.Timestamp{Timestamp: authentication.CreatedAt}, &fliptsql.Timestamp{Timestamp: authentication.UpdatedAt}, ). @@ -137,15 +134,15 @@ func (s *Store) CreateAuthentication(ctx context.Context, r *storage.CreateAuthe // // Given a row is present for the hash of the clientToken then materialize into an Authentication. // Else, given it cannot be located, a storage.ErrNotFound error is wrapped and returned instead. -func (s *Store) GetAuthenticationByClientToken(ctx context.Context, clientToken string) (*auth.Authentication, error) { - hashedToken, err := hashClientToken(clientToken) +func (s *Store) GetAuthenticationByClientToken(ctx context.Context, clientToken string) (*rpcauth.Authentication, error) { + hashedToken, err := auth.HashClientToken(clientToken) if err != nil { return nil, fmt.Errorf("getting authentication by token: %w", err) } var ( - authentication auth.Authentication - expiresAt fliptsql.Timestamp + authentication rpcauth.Authentication + expiresAt fliptsql.NullableTimestamp createdAt fliptsql.Timestamp updatedAt fliptsql.Timestamp ) @@ -181,43 +178,3 @@ func (s *Store) GetAuthenticationByClientToken(ctx context.Context, clientToken return &authentication, nil } - -const decodedTokenLen = 32 - -// generateRandomToken produces a URL safe base64 encoded string of random characters -// the data is sourced from a pseudo-random input stream -func generateRandomToken() string { - var token [decodedTokenLen]byte - if _, err := rand.Read(token[:]); err != nil { - panic(err) - } - - return base64.URLEncoding.EncodeToString(token[:]) -} - -// hashClientToken performs a SHA256 sum on the input string -// it returns the result as a URL safe base64 encoded string -func hashClientToken(token string) (string, error) { - // produce SHA256 hash of token - hash := sha256.New() - if _, err := hash.Write([]byte(token)); err != nil { - return "", fmt.Errorf("hashing client token: %w", err) - } - - // base64(sha256sum) - var ( - data = make([]byte, 0, base64.URLEncoding.EncodedLen(hash.Size())) - buf = bytes.NewBuffer(data) - enc = base64.NewEncoder(base64.URLEncoding, buf) - ) - - if _, err := enc.Write(hash.Sum(nil)); err != nil { - return "", fmt.Errorf("hashing client token: %w", err) - } - - if err := enc.Close(); err != nil { - return "", fmt.Errorf("hashing client token: %w", err) - } - - return buf.String(), nil -} diff --git a/internal/storage/sql/auth/store_test.go b/internal/storage/auth/sql/store_test.go similarity index 83% rename from internal/storage/sql/auth/store_test.go rename to internal/storage/auth/sql/store_test.go index 444299c25b..44f93fe61d 100644 --- a/internal/storage/sql/auth/store_test.go +++ b/internal/storage/auth/sql/store_test.go @@ -1,8 +1,7 @@ -package auth +package sql import ( "context" - "encoding/base64" "fmt" "os" "testing" @@ -10,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.flipt.io/flipt/errors" "go.flipt.io/flipt/internal/storage" fliptsql "go.flipt.io/flipt/internal/storage/sql" fliptsqltesting "go.flipt.io/flipt/internal/storage/sql/testing" @@ -54,7 +54,7 @@ func TestAuthentication_CreateAuthentication(t *testing.T) { name string opts func(t *testing.T) []Option req *storage.CreateAuthenticationRequest - expectedErrIs error + expectedErrAs error expectedToken string expectedAuthentication *auth.Authentication }{ @@ -82,6 +82,28 @@ func TestAuthentication_CreateAuthentication(t *testing.T) { UpdatedAt: someTimestamp, }, }, + { + name: "successfully creates authentication (no expiration)", + opts: commonOpts, + req: &storage.CreateAuthenticationRequest{ + Method: auth.Method_TOKEN, + Metadata: map[string]string{ + "io.flipt.auth.token.name": "access_all_areas", + "io.flipt.auth.token.description": "The keys to the castle", + }, + }, + expectedToken: "token:TestAuthentication_CreateAuthentication/successfully_creates_authentication_(no_expiration)", + expectedAuthentication: &auth.Authentication{ + Id: "id:TestAuthentication_CreateAuthentication/successfully_creates_authentication_(no_expiration)", + Method: auth.Method_TOKEN, + Metadata: map[string]string{ + "io.flipt.auth.token.name": "access_all_areas", + "io.flipt.auth.token.description": "The keys to the castle", + }, + CreatedAt: someTimestamp, + UpdatedAt: someTimestamp, + }, + }, { name: "fails ID uniqueness constraint", opts: func(t *testing.T) []Option { @@ -100,7 +122,7 @@ func TestAuthentication_CreateAuthentication(t *testing.T) { "io.flipt.auth.token.description": "The keys to the castle", }, }, - expectedErrIs: storage.ErrInvalid, + expectedErrAs: errPtr(errors.ErrInvalid("")), }, { name: "fails token uniqueness constraint", @@ -120,7 +142,7 @@ func TestAuthentication_CreateAuthentication(t *testing.T) { "io.flipt.auth.token.description": "The keys to the castle", }, }, - expectedErrIs: storage.ErrInvalid, + expectedErrAs: errPtr(errors.ErrInvalid("")), }, } { test := test @@ -128,8 +150,8 @@ func TestAuthentication_CreateAuthentication(t *testing.T) { store := storeFn(test.opts(t)...) clientToken, created, err := store.CreateAuthentication(ctx, test.req) - if test.expectedErrIs != nil { - require.ErrorIs(t, err, test.expectedErrIs) + if test.expectedErrAs != nil { + require.ErrorAs(t, err, test.expectedErrAs) return } @@ -151,13 +173,13 @@ func TestAuthentication_GetAuthenticationByClientToken(t *testing.T) { for _, test := range []struct { name string clientToken string - expectedErrIs error + expectedErrAs error expectedAuthentication *auth.Authentication }{ { name: "error not found for unexpected clientToken", clientToken: "unknown", - expectedErrIs: storage.ErrNotFound, + expectedErrAs: errPtr(errors.ErrNotFound("")), }, { name: "successfully retrieves authentication by clientToken", @@ -177,14 +199,14 @@ func TestAuthentication_GetAuthenticationByClientToken(t *testing.T) { } { var ( clientToken = test.clientToken - expectedErrIs = test.expectedErrIs + expectedErrAs = test.expectedErrAs expectedAuthentication = test.expectedAuthentication ) t.Run(test.name, func(t *testing.T) { retrieved, err := storeFn(commonOpts(t)...).GetAuthenticationByClientToken(ctx, clientToken) - if expectedErrIs != nil { - require.ErrorIs(t, err, expectedErrIs) + if expectedErrAs != nil { + require.ErrorAs(t, err, expectedErrAs) return } @@ -194,27 +216,6 @@ func TestAuthentication_GetAuthenticationByClientToken(t *testing.T) { } } -func FuzzHashClientToken(f *testing.F) { - for _, seed := range []string{ - "hello, world", - "supersecretstring", - "egGpvIxtdG6tI3OIJjXOrv7xZW3hRMYg/Lt/G6X/UEwC", - } { - f.Add(seed) - } - for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} { - f.Add(string(seed)) - } - f.Fuzz(func(t *testing.T, token string) { - hashed, err := hashClientToken(token) - require.NoError(t, err) - require.NotEmpty(t, hashed, "hashed result is empty") - - _, err = base64.URLEncoding.DecodeString(hashed) - require.NoError(t, err) - }) -} - type authentication struct { id string token string @@ -274,3 +275,7 @@ func newStaticGenerator(t *testing.T, purpose string) func() string { return fmt.Sprintf("%s:%s", purpose, t.Name()) } } + +func errPtr[E error](e E) *E { + return &e +} diff --git a/internal/storage/sql/errors.go b/internal/storage/sql/errors.go index a490d9953c..33b14b3e7a 100644 --- a/internal/storage/sql/errors.go +++ b/internal/storage/sql/errors.go @@ -3,17 +3,18 @@ package sql import ( "database/sql" "errors" - "fmt" "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/mattn/go-sqlite3" - "go.flipt.io/flipt/internal/storage" + flipterrors "go.flipt.io/flipt/errors" ) var ( - errNotUnique = fmt.Errorf("not unique: %w", storage.ErrInvalid) - errForeignKeyNotFound = fmt.Errorf("associated resource: %w", storage.ErrNotFound) + errNotFound = flipterrors.ErrNotFound("resource") + errConstraintViolated = flipterrors.ErrInvalid("contraint violated") + errNotUnique = flipterrors.ErrInvalid("not unique") + errForeignKeyNotFound = flipterrors.ErrNotFound("associated resource not found") ) // AdaptError converts specific known-driver errors into wrapped storage errors. @@ -23,7 +24,7 @@ func (d Driver) AdaptError(err error) error { } if errors.Is(err, sql.ErrNoRows) { - return storage.ErrNotFound + return errNotFound } switch d { @@ -50,7 +51,7 @@ func adaptSQLiteError(err error) error { return errNotUnique } - return storage.ErrInvalid + return errConstraintViolated } } diff --git a/internal/storage/sql/errors_test.go b/internal/storage/sql/errors_test.go index c73a11c548..94390db257 100644 --- a/internal/storage/sql/errors_test.go +++ b/internal/storage/sql/errors_test.go @@ -9,15 +9,19 @@ import ( "github.com/lib/pq" "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/require" - "go.flipt.io/flipt/internal/storage" + "go.flipt.io/flipt/errors" ) +func errPtr[E error](e E) *E { + return &e +} + func Test_AdaptError(t *testing.T) { for _, test := range []struct { driver Driver inputErr error - // if outputErrIs nil then test will ensure input is returned - outputErrIs error + // if outputErrAs nil then test will ensure input is returned + outputErrAs any }{ // No driver {}, @@ -25,32 +29,32 @@ func Test_AdaptError(t *testing.T) { { driver: SQLite, inputErr: sql.ErrNoRows, - outputErrIs: storage.ErrNotFound, + outputErrAs: errPtr(errors.ErrNotFound("")), }, { driver: SQLite, inputErr: fmt.Errorf("wrapped no rows: %w", sql.ErrNoRows), - outputErrIs: storage.ErrNotFound, + outputErrAs: errPtr(errors.ErrNotFound("")), }, { driver: Postgres, inputErr: sql.ErrNoRows, - outputErrIs: storage.ErrNotFound, + outputErrAs: errPtr(errors.ErrNotFound("")), }, { driver: Postgres, inputErr: fmt.Errorf("wrapped no rows: %w", sql.ErrNoRows), - outputErrIs: storage.ErrNotFound, + outputErrAs: errPtr(errors.ErrNotFound("")), }, { driver: MySQL, inputErr: sql.ErrNoRows, - outputErrIs: storage.ErrNotFound, + outputErrAs: errPtr(errors.ErrNotFound("")), }, { driver: MySQL, inputErr: fmt.Errorf("wrapped no rows: %w", sql.ErrNoRows), - outputErrIs: storage.ErrNotFound, + outputErrAs: errPtr(errors.ErrNotFound("")), }, // SQLite // Unchanged errors @@ -70,7 +74,7 @@ func Test_AdaptError(t *testing.T) { Code: sqlite3.ErrConstraint, ExtendedCode: sqlite3.ErrConstraintCheck, }, - outputErrIs: storage.ErrInvalid, + outputErrAs: errPtr(errors.ErrInvalid("")), }, { driver: SQLite, @@ -78,7 +82,7 @@ func Test_AdaptError(t *testing.T) { Code: sqlite3.ErrConstraint, ExtendedCode: sqlite3.ErrConstraintForeignKey, }, - outputErrIs: storage.ErrNotFound, + outputErrAs: errPtr(errors.ErrNotFound("")), }, { driver: SQLite, @@ -86,7 +90,7 @@ func Test_AdaptError(t *testing.T) { Code: sqlite3.ErrConstraint, ExtendedCode: sqlite3.ErrConstraintUnique, }, - outputErrIs: storage.ErrInvalid, + outputErrAs: errPtr(errors.ErrInvalid("")), }, // Postgres // Unchanged errors @@ -104,13 +108,13 @@ func Test_AdaptError(t *testing.T) { driver: Postgres, // foreign_key_violation inputErr: &pq.Error{Code: pq.ErrorCode("23503")}, - outputErrIs: storage.ErrNotFound, + outputErrAs: errPtr(errors.ErrNotFound("")), }, { driver: Postgres, // unique_violation inputErr: &pq.Error{Code: pq.ErrorCode("23505")}, - outputErrIs: storage.ErrInvalid, + outputErrAs: errPtr(errors.ErrInvalid("")), }, // MySQL // Unchanged errors @@ -128,20 +132,27 @@ func Test_AdaptError(t *testing.T) { driver: MySQL, // foreign_key_violation inputErr: &mysql.MySQLError{Number: uint16(1452)}, - outputErrIs: storage.ErrNotFound, + outputErrAs: errPtr(errors.ErrNotFound("")), }, { driver: MySQL, // unique_violation inputErr: &mysql.MySQLError{Number: uint16(1062)}, - outputErrIs: storage.ErrInvalid, + outputErrAs: errPtr(errors.ErrInvalid("")), }, } { test := test - name := fmt.Sprintf("(%v).AdaptError(%T) == %T", test.driver, test.inputErr, test.outputErrIs) + + outputs := test.outputErrAs + if outputs == nil { + outputs = test.inputErr + } + + name := fmt.Sprintf("(%v).AdaptError(%v) == %T", test.driver, test.inputErr, outputs) + t.Run(name, func(t *testing.T) { err := test.driver.AdaptError(test.inputErr) - if test.outputErrIs == nil { + if test.outputErrAs == nil { // given the output expectation is nil we ensure the input error // is returned unchanged require.Equal(t, test.inputErr, err, "input error was changed unexpectedly") @@ -149,7 +160,7 @@ func Test_AdaptError(t *testing.T) { } // otherwise, we ensure returned error matches via errors.Is - require.ErrorIs(t, err, test.outputErrIs) + require.ErrorAs(t, err, test.outputErrAs) }) } } diff --git a/internal/storage/sql/fields.go b/internal/storage/sql/fields.go index 8d85c3d0a1..89f206e6f6 100644 --- a/internal/storage/sql/fields.go +++ b/internal/storage/sql/fields.go @@ -30,6 +30,33 @@ func (t *Timestamp) Value() (driver.Value, error) { return t.Timestamp.AsTime(), t.Timestamp.CheckValid() } +type NullableTimestamp Timestamp + +func (t *NullableTimestamp) Scan(value interface{}) error { + if value == nil { + return nil + } + + if v, ok := value.(time.Time); ok { + val := timestamppb.New(v) + if err := val.CheckValid(); err != nil { + return err + } + + t.Timestamp = val + } + + return nil +} + +func (t *NullableTimestamp) Value() (driver.Value, error) { + if t.Timestamp == nil { + return nil, nil + } + + return t.Timestamp.AsTime(), t.Timestamp.CheckValid() +} + type JSONField[T any] struct { T T } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 8b542eb3f3..914c96beec 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -2,7 +2,6 @@ package storage import ( "context" - "errors" "fmt" "go.flipt.io/flipt/rpc/flipt" @@ -10,14 +9,6 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) -var ( - // ErrNotFound is returned when a resource is requested by a key identifier - // and it could not be found in the backing store. - ErrNotFound = errors.New("resource not found") - // ErrInvalid is returned when an invalid attempt is made to persist a resource - ErrInvalid = errors.New("resource is invalid") -) - // EvaluationRule represents a rule and constraints required for evaluating if a // given flagKey matches a segment type EvaluationRule struct { diff --git a/rpc/flipt/auth/auth.pb.go b/rpc/flipt/auth/auth.pb.go index c4cefb23fd..6ed53de6ac 100644 --- a/rpc/flipt/auth/auth.pb.go +++ b/rpc/flipt/auth/auth.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.28.1-devel // protoc (unknown) // source: auth/auth.proto @@ -155,6 +155,124 @@ func (x *Authentication) GetMetadata() map[string]string { return nil } +type CreateTokenRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` +} + +func (x *CreateTokenRequest) Reset() { + *x = CreateTokenRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_auth_auth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTokenRequest) ProtoMessage() {} + +func (x *CreateTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_auth_auth_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTokenRequest.ProtoReflect.Descriptor instead. +func (*CreateTokenRequest) Descriptor() ([]byte, []int) { + return file_auth_auth_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateTokenRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateTokenRequest) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *CreateTokenRequest) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + +type CreateTokenResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClientToken string `protobuf:"bytes,1,opt,name=client_token,json=clientToken,proto3" json:"client_token,omitempty"` + Authentication *Authentication `protobuf:"bytes,2,opt,name=authentication,proto3" json:"authentication,omitempty"` +} + +func (x *CreateTokenResponse) Reset() { + *x = CreateTokenResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_auth_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTokenResponse) ProtoMessage() {} + +func (x *CreateTokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_auth_auth_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTokenResponse.ProtoReflect.Descriptor instead. +func (*CreateTokenResponse) Descriptor() ([]byte, []int) { + return file_auth_auth_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateTokenResponse) GetClientToken() string { + if x != nil { + return x.ClientToken + } + return "" +} + +func (x *CreateTokenResponse) GetAuthentication() *Authentication { + if x != nil { + return x.Authentication + } + return nil +} + var File_auth_auth_proto protoreflect.FileDescriptor var file_auth_auth_proto_rawDesc = []byte{ @@ -189,28 +307,59 @@ var file_auth_auth_proto_rawDesc = []byte{ 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x2a, 0x1d, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x08, 0x0a, 0x04, 0x4e, - 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x01, - 0x42, 0xaa, 0x02, 0x5a, 0x20, 0x67, 0x6f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x69, 0x6f, - 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, - 0x2f, 0x61, 0x75, 0x74, 0x68, 0x92, 0x41, 0x84, 0x02, 0x12, 0xb0, 0x01, 0x0a, 0x19, 0x46, 0x6c, - 0x69, 0x70, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x20, 0x41, 0x50, 0x49, 0x73, 0x22, 0x3d, 0x0a, 0x0a, 0x46, 0x6c, 0x69, 0x70, 0x74, - 0x20, 0x54, 0x65, 0x61, 0x6d, 0x12, 0x21, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2d, - 0x69, 0x6f, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x1a, 0x0c, 0x64, 0x65, 0x76, 0x40, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2e, 0x69, 0x6f, 0x2a, 0x4c, 0x0a, 0x0b, 0x4d, 0x49, 0x54, 0x20, 0x4c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2d, - 0x69, 0x6f, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, - 0x69, 0x6e, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x4c, 0x49, 0x43, - 0x45, 0x4e, 0x53, 0x45, 0x32, 0x06, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x2a, 0x02, 0x01, 0x02, - 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, - 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, - 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x27, 0x0a, 0x0a, 0x46, 0x6c, 0x69, 0x70, 0x74, 0x20, 0x44, 0x6f, - 0x63, 0x73, 0x12, 0x19, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x6f, 0x63, 0x73, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x01, 0x22, 0xa1, 0x01, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, + 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x3a, 0x1a, 0x92, 0x41, 0x17, 0x0a, 0x15, + 0xd2, 0x01, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0xd2, 0x01, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x7c, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x42, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2a, 0x1d, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x08, 0x0a, + 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x4f, 0x4b, 0x45, 0x4e, + 0x10, 0x01, 0x32, 0xc9, 0x01, 0x0a, 0x20, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xa4, 0x01, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x54, 0x92, 0x41, 0x51, 0x0a, 0x2a, 0x61, + 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x75, + 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x1b, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2a, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0xaa, + 0x02, 0x5a, 0x20, 0x67, 0x6f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x69, 0x6f, 0x2f, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x61, + 0x75, 0x74, 0x68, 0x92, 0x41, 0x84, 0x02, 0x12, 0xb0, 0x01, 0x0a, 0x19, 0x46, 0x6c, 0x69, 0x70, + 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x41, 0x50, 0x49, 0x73, 0x22, 0x3d, 0x0a, 0x0a, 0x46, 0x6c, 0x69, 0x70, 0x74, 0x20, 0x54, + 0x65, 0x61, 0x6d, 0x12, 0x21, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2d, 0x69, 0x6f, + 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x1a, 0x0c, 0x64, 0x65, 0x76, 0x40, 0x66, 0x6c, 0x69, 0x70, + 0x74, 0x2e, 0x69, 0x6f, 0x2a, 0x4c, 0x0a, 0x0b, 0x4d, 0x49, 0x54, 0x20, 0x4c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2d, 0x69, 0x6f, + 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x69, 0x6e, + 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, + 0x53, 0x45, 0x32, 0x06, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, + 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, + 0x6f, 0x6e, 0x72, 0x27, 0x0a, 0x0a, 0x46, 0x6c, 0x69, 0x70, 0x74, 0x20, 0x44, 0x6f, 0x63, 0x73, + 0x12, 0x19, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x66, 0x6c, + 0x69, 0x70, 0x74, 0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x6f, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -226,24 +375,30 @@ func file_auth_auth_proto_rawDescGZIP() []byte { } var file_auth_auth_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_auth_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_auth_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_auth_auth_proto_goTypes = []interface{}{ (Method)(0), // 0: flipt.auth.Method (*Authentication)(nil), // 1: flipt.auth.Authentication - nil, // 2: flipt.auth.Authentication.MetadataEntry - (*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp + (*CreateTokenRequest)(nil), // 2: flipt.auth.CreateTokenRequest + (*CreateTokenResponse)(nil), // 3: flipt.auth.CreateTokenResponse + nil, // 4: flipt.auth.Authentication.MetadataEntry + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp } var file_auth_auth_proto_depIdxs = []int32{ 0, // 0: flipt.auth.Authentication.method:type_name -> flipt.auth.Method - 3, // 1: flipt.auth.Authentication.expires_at:type_name -> google.protobuf.Timestamp - 3, // 2: flipt.auth.Authentication.created_at:type_name -> google.protobuf.Timestamp - 3, // 3: flipt.auth.Authentication.updated_at:type_name -> google.protobuf.Timestamp - 2, // 4: flipt.auth.Authentication.metadata:type_name -> flipt.auth.Authentication.MetadataEntry - 5, // [5:5] is the sub-list for method output_type - 5, // [5:5] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 5, // 1: flipt.auth.Authentication.expires_at:type_name -> google.protobuf.Timestamp + 5, // 2: flipt.auth.Authentication.created_at:type_name -> google.protobuf.Timestamp + 5, // 3: flipt.auth.Authentication.updated_at:type_name -> google.protobuf.Timestamp + 4, // 4: flipt.auth.Authentication.metadata:type_name -> flipt.auth.Authentication.MetadataEntry + 5, // 5: flipt.auth.CreateTokenRequest.expires_at:type_name -> google.protobuf.Timestamp + 1, // 6: flipt.auth.CreateTokenResponse.authentication:type_name -> flipt.auth.Authentication + 2, // 7: flipt.auth.AuthenticationMethodTokenService.CreateToken:input_type -> flipt.auth.CreateTokenRequest + 3, // 8: flipt.auth.AuthenticationMethodTokenService.CreateToken:output_type -> flipt.auth.CreateTokenResponse + 8, // [8:9] is the sub-list for method output_type + 7, // [7:8] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_auth_auth_proto_init() } @@ -264,6 +419,30 @@ func file_auth_auth_proto_init() { return nil } } + file_auth_auth_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTokenRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_auth_auth_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTokenResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -271,9 +450,9 @@ func file_auth_auth_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_auth_auth_proto_rawDesc, NumEnums: 1, - NumMessages: 2, + NumMessages: 4, NumExtensions: 0, - NumServices: 0, + NumServices: 1, }, GoTypes: file_auth_auth_proto_goTypes, DependencyIndexes: file_auth_auth_proto_depIdxs, diff --git a/rpc/flipt/auth/auth.pb.gw.go b/rpc/flipt/auth/auth.pb.gw.go new file mode 100644 index 0000000000..2675cb0e24 --- /dev/null +++ b/rpc/flipt/auth/auth.pb.gw.go @@ -0,0 +1,171 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: auth/auth.proto + +/* +Package auth is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package auth + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_AuthenticationMethodTokenService_CreateToken_0(ctx context.Context, marshaler runtime.Marshaler, client AuthenticationMethodTokenServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateTokenRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.CreateToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthenticationMethodTokenService_CreateToken_0(ctx context.Context, marshaler runtime.Marshaler, server AuthenticationMethodTokenServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateTokenRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.CreateToken(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterAuthenticationMethodTokenServiceHandlerServer registers the http handlers for service AuthenticationMethodTokenService to "mux". +// UnaryRPC :call AuthenticationMethodTokenServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAuthenticationMethodTokenServiceHandlerFromEndpoint instead. +func RegisterAuthenticationMethodTokenServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthenticationMethodTokenServiceServer) error { + + mux.Handle("POST", pattern_AuthenticationMethodTokenService_CreateToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/flipt.auth.AuthenticationMethodTokenService/CreateToken", runtime.WithHTTPPathPattern("/auth/v1/method/token")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthenticationMethodTokenService_CreateToken_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthenticationMethodTokenService_CreateToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterAuthenticationMethodTokenServiceHandlerFromEndpoint is same as RegisterAuthenticationMethodTokenServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterAuthenticationMethodTokenServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterAuthenticationMethodTokenServiceHandler(ctx, mux, conn) +} + +// RegisterAuthenticationMethodTokenServiceHandler registers the http handlers for service AuthenticationMethodTokenService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterAuthenticationMethodTokenServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterAuthenticationMethodTokenServiceHandlerClient(ctx, mux, NewAuthenticationMethodTokenServiceClient(conn)) +} + +// RegisterAuthenticationMethodTokenServiceHandlerClient registers the http handlers for service AuthenticationMethodTokenService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AuthenticationMethodTokenServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AuthenticationMethodTokenServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "AuthenticationMethodTokenServiceClient" to call the correct interceptors. +func RegisterAuthenticationMethodTokenServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AuthenticationMethodTokenServiceClient) error { + + mux.Handle("POST", pattern_AuthenticationMethodTokenService_CreateToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/flipt.auth.AuthenticationMethodTokenService/CreateToken", runtime.WithHTTPPathPattern("/auth/v1/method/token")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthenticationMethodTokenService_CreateToken_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthenticationMethodTokenService_CreateToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_AuthenticationMethodTokenService_CreateToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"auth", "v1", "method", "token"}, "")) +) + +var ( + forward_AuthenticationMethodTokenService_CreateToken_0 = runtime.ForwardResponseMessage +) diff --git a/rpc/flipt/auth/auth.proto b/rpc/flipt/auth/auth.proto index ce6171aad4..65b941e939 100644 --- a/rpc/flipt/auth/auth.proto +++ b/rpc/flipt/auth/auth.proto @@ -44,3 +44,30 @@ message Authentication { google.protobuf.Timestamp updated_at = 5; map metadata = 6; } + +message CreateTokenRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + required: ["name", "description"] + } + }; + + string name = 1; + string description = 2; + google.protobuf.Timestamp expires_at = 3; +} + +message CreateTokenResponse { + string client_token = 1; + Authentication authentication = 2; +} + +service AuthenticationMethodTokenService { + rpc CreateToken(CreateTokenRequest) returns (CreateTokenResponse) { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + operation_id: "create", + description: "Create authentication token", + tags: "authentication authentication_method token", + }; + } +} diff --git a/rpc/flipt/auth/auth_grpc.pb.go b/rpc/flipt/auth/auth_grpc.pb.go new file mode 100644 index 0000000000..ad47354971 --- /dev/null +++ b/rpc/flipt/auth/auth_grpc.pb.go @@ -0,0 +1,106 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc (unknown) +// source: auth/auth.proto + +package auth + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AuthenticationMethodTokenServiceClient is the client API for AuthenticationMethodTokenService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AuthenticationMethodTokenServiceClient interface { + CreateToken(ctx context.Context, in *CreateTokenRequest, opts ...grpc.CallOption) (*CreateTokenResponse, error) +} + +type authenticationMethodTokenServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthenticationMethodTokenServiceClient(cc grpc.ClientConnInterface) AuthenticationMethodTokenServiceClient { + return &authenticationMethodTokenServiceClient{cc} +} + +func (c *authenticationMethodTokenServiceClient) CreateToken(ctx context.Context, in *CreateTokenRequest, opts ...grpc.CallOption) (*CreateTokenResponse, error) { + out := new(CreateTokenResponse) + err := c.cc.Invoke(ctx, "/flipt.auth.AuthenticationMethodTokenService/CreateToken", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthenticationMethodTokenServiceServer is the server API for AuthenticationMethodTokenService service. +// All implementations must embed UnimplementedAuthenticationMethodTokenServiceServer +// for forward compatibility +type AuthenticationMethodTokenServiceServer interface { + CreateToken(context.Context, *CreateTokenRequest) (*CreateTokenResponse, error) + mustEmbedUnimplementedAuthenticationMethodTokenServiceServer() +} + +// UnimplementedAuthenticationMethodTokenServiceServer must be embedded to have forward compatible implementations. +type UnimplementedAuthenticationMethodTokenServiceServer struct { +} + +func (UnimplementedAuthenticationMethodTokenServiceServer) CreateToken(context.Context, *CreateTokenRequest) (*CreateTokenResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateToken not implemented") +} +func (UnimplementedAuthenticationMethodTokenServiceServer) mustEmbedUnimplementedAuthenticationMethodTokenServiceServer() { +} + +// UnsafeAuthenticationMethodTokenServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AuthenticationMethodTokenServiceServer will +// result in compilation errors. +type UnsafeAuthenticationMethodTokenServiceServer interface { + mustEmbedUnimplementedAuthenticationMethodTokenServiceServer() +} + +func RegisterAuthenticationMethodTokenServiceServer(s grpc.ServiceRegistrar, srv AuthenticationMethodTokenServiceServer) { + s.RegisterService(&AuthenticationMethodTokenService_ServiceDesc, srv) +} + +func _AuthenticationMethodTokenService_CreateToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateTokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthenticationMethodTokenServiceServer).CreateToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/flipt.auth.AuthenticationMethodTokenService/CreateToken", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthenticationMethodTokenServiceServer).CreateToken(ctx, req.(*CreateTokenRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AuthenticationMethodTokenService_ServiceDesc is the grpc.ServiceDesc for AuthenticationMethodTokenService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AuthenticationMethodTokenService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "flipt.auth.AuthenticationMethodTokenService", + HandlerType: (*AuthenticationMethodTokenServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateToken", + Handler: _AuthenticationMethodTokenService_CreateToken_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "auth/auth.proto", +} diff --git a/rpc/flipt/flipt.yaml b/rpc/flipt/flipt.yaml index dcf3802b82..757ee02bba 100644 --- a/rpc/flipt/flipt.yaml +++ b/rpc/flipt/flipt.yaml @@ -72,3 +72,9 @@ http: body: "*" - selector: flipt.Flipt.DeleteConstraint delete: /api/v1/segments/{segment_key}/constraints/{id} + # authentication methods + # + # method: token + - selector: flipt.auth.AuthenticationMethodTokenService.CreateToken + post: /auth/v1/method/token + body: "*" diff --git a/swagger/auth/auth.swagger.json b/swagger/auth/auth.swagger.json index b8a6670778..39ef1ad9cd 100644 --- a/swagger/auth/auth.swagger.json +++ b/swagger/auth/auth.swagger.json @@ -13,6 +13,11 @@ "url": "https://github.com/flipt-io/flipt/blob/main/rpc/flipt/LICENSE" } }, + "tags": [ + { + "name": "AuthenticationMethodTokenService" + } + ], "schemes": [ "http", "https" @@ -23,8 +28,109 @@ "produces": [ "application/json" ], - "paths": {}, + "paths": { + "/auth/v1/method/token": { + "post": { + "description": "Create authentication token", + "operationId": "create", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/authCreateTokenResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/authCreateTokenRequest" + } + } + ], + "tags": [ + "authentication authentication_method token" + ] + } + } + }, "definitions": { + "authAuthentication": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "method": { + "$ref": "#/definitions/authMethod" + }, + "expiresAt": { + "type": "string", + "format": "date-time" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "authCreateTokenRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "expiresAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "name", + "description" + ] + }, + "authCreateTokenResponse": { + "type": "object", + "properties": { + "clientToken": { + "type": "string" + }, + "authentication": { + "$ref": "#/definitions/authAuthentication" + } + } + }, + "authMethod": { + "type": "string", + "enum": [ + "NONE", + "TOKEN" + ], + "default": "NONE" + }, "protobufAny": { "type": "object", "properties": {