From 719a462048772f2d3b5c92b60f347b7717f85105 Mon Sep 17 00:00:00 2001 From: mitchell Date: Sun, 14 Apr 2019 20:56:55 -0700 Subject: [PATCH] First iteration of selfpass gRPC backend, w/ credentials CRUD functional --- .gitignore | 2 + Makefile | 21 + cmd/server/server.go | 90 ++++ credentials/endpoints/endpoints.go | 67 +++ credentials/protobuf/service.pb.go | 751 +++++++++++++++++++++++++++ credentials/protobuf/service.proto | 62 +++ credentials/repositories/dynamodb.go | 118 +++++ credentials/service/service.go | 84 +++ credentials/transport/encoding.go | 127 +++++ credentials/transport/grpc_server.go | 146 ++++++ credentials/types/credential.go | 36 ++ credentials/types/error_prefixes.go | 8 + credentials/types/interfaces.go | 20 + go.mod | 14 + go.sum | 64 +++ 15 files changed, 1610 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 cmd/server/server.go create mode 100644 credentials/endpoints/endpoints.go create mode 100644 credentials/protobuf/service.pb.go create mode 100644 credentials/protobuf/service.proto create mode 100644 credentials/repositories/dynamodb.go create mode 100644 credentials/service/service.go create mode 100644 credentials/transport/encoding.go create mode 100644 credentials/transport/grpc_server.go create mode 100644 credentials/types/credential.go create mode 100644 credentials/types/error_prefixes.go create mode 100644 credentials/types/interfaces.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25c3120 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +bin \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..80f3cbc --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +.PHONY: all build clean format test + +build: clean format test + go build -o ./bin/server ./cmd/server/server.go + +clean: + rm -rf ./bin + go mod tidy + +dev: build + ./bin/server --dev + +format: + goimports -w -l . + +gen-protoc: + protoc --go_out=plugins=grpc:. \ + ./credentials/protobuf/service.proto + +test: + go test -cover ./... diff --git a/cmd/server/server.go b/cmd/server/server.go new file mode 100644 index 0000000..60dd0ff --- /dev/null +++ b/cmd/server/server.go @@ -0,0 +1,90 @@ +package main + +import ( + "flag" + "io" + "net" + "os" + "os/signal" + + "github.com/go-kit/kit/log" + "github.com/mitchell/selfpass/credentials/protobuf" + "github.com/mitchell/selfpass/credentials/repositories" + "github.com/mitchell/selfpass/credentials/service" + "github.com/mitchell/selfpass/credentials/transport" + "google.golang.org/grpc" +) + +var logger log.Logger + +func main() { + var ( + stop = make(chan os.Signal) + dev = flag.Bool("dev", false, "enables dev mode logging") + port = flag.String("port", "8080", "specify the port to listen on") + tableName = flag.String( + "credential-table-name", + "selfpass-credential", + "specify the credential table name on AWS", + ) + ) + + signal.Notify(stop, os.Interrupt) + flag.Parse() + + logger = newLogger(os.Stdout, *dev) + + var ( + db = repositories.NewDynamoTable(*tableName) + svc = service.NewCredentials(db) + gsrv = transport.NewGRPCServer(svc, logger) + srv = grpc.NewServer() + ) + protobuf.RegisterCredentialServiceServer(srv, gsrv) + + addr := "0.0.0.0:" + *port + lis, err := net.Listen("tcp", addr) + check(err) + + go func() { + logger.Log( + "message", "serving", + "address", addr, + "credentialTable", tableName, + "dev", dev, + ) + check(srv.Serve(lis)) + }() + + <-stop + logger.Log("message", "gracefully stopping") + srv.GracefulStop() +} + +func newLogger(writer io.Writer, dev bool) log.Logger { + var l log.Logger + writer = log.NewSyncWriter(writer) + + if dev { + l = log.NewLogfmtLogger(writer) + } else { + l = log.NewJSONLogger(writer) + } + l = log.WithPrefix(l, "caller", log.DefaultCaller, "timestamp", log.DefaultTimestamp) + + lfunc := log.LoggerFunc(func(in ...interface{}) error { + if err := l.Log(in...); err != nil { + panic(err.Error()) + } + return nil + }) + + return lfunc +} + +func check(err error) { + if err != nil { + logger.Log("error", err) + os.Exit(1) + } +} diff --git a/credentials/endpoints/endpoints.go b/credentials/endpoints/endpoints.go new file mode 100644 index 0000000..82447fe --- /dev/null +++ b/credentials/endpoints/endpoints.go @@ -0,0 +1,67 @@ +package endpoints + +import ( + "context" + + "github.com/go-kit/kit/endpoint" + "github.com/mitchell/selfpass/credentials/types" +) + +// MakeGetAllMetadataEndpoint TODO +func MakeGetAllMetadataEndpoint(svc types.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + r := request.(GetAllMetadataRequest) + + mdch, errch := svc.GetAllMetadata(ctx, r.SourceHost) + + return MetadataStream{ + Metadata: mdch, + Errors: errch, + }, nil + } +} + +// MakeCreateEndpoint TODO +func MakeCreateEndpoint(svc types.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + r := request.(types.CredentialInput) + return svc.Create(ctx, r) + } +} + +// MakeUpdateEndpoint TODO +func MakeUpdateEndpoint(svc types.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + r := request.(UpdateRequest) + return svc.Update(ctx, r.ID, r.Credential) + } +} + +func MakeDeleteEndpoint(svc types.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + r := request.(IDRequest) + return nil, svc.Delete(ctx, r.ID) + } +} + +// IDRequest TODO +type IDRequest struct { + ID string +} + +// GetAllMetadataRequest TODO +type GetAllMetadataRequest struct { + SourceHost string +} + +// MetadataStream TODO +type MetadataStream struct { + Metadata <-chan types.Metadata + Errors chan error +} + +// UpdateRequest TODO +type UpdateRequest struct { + ID string + Credential types.CredentialInput +} diff --git a/credentials/protobuf/service.pb.go b/credentials/protobuf/service.pb.go new file mode 100644 index 0000000..8de6254 --- /dev/null +++ b/credentials/protobuf/service.pb.go @@ -0,0 +1,751 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: credentials/protobuf/service.proto + +package protobuf + +import ( + context "context" + fmt "fmt" + math "math" + + proto "github.com/golang/protobuf/proto" + timestamp "github.com/golang/protobuf/ptypes/timestamp" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type DeleteResponse struct { + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DeleteResponse) Reset() { *m = DeleteResponse{} } +func (m *DeleteResponse) String() string { return proto.CompactTextString(m) } +func (*DeleteResponse) ProtoMessage() {} +func (*DeleteResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ad34efc7bbd96e69, []int{0} +} + +func (m *DeleteResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DeleteResponse.Unmarshal(m, b) +} +func (m *DeleteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DeleteResponse.Marshal(b, m, deterministic) +} +func (m *DeleteResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeleteResponse.Merge(m, src) +} +func (m *DeleteResponse) XXX_Size() int { + return xxx_messageInfo_DeleteResponse.Size(m) +} +func (m *DeleteResponse) XXX_DiscardUnknown() { + xxx_messageInfo_DeleteResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_DeleteResponse proto.InternalMessageInfo + +func (m *DeleteResponse) GetSuccess() bool { + if m != nil { + return m.Success + } + return false +} + +type GetAllMetadataRequest struct { + SourceHost string `protobuf:"bytes,1,opt,name=source_host,json=sourceHost,proto3" json:"source_host,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetAllMetadataRequest) Reset() { *m = GetAllMetadataRequest{} } +func (m *GetAllMetadataRequest) String() string { return proto.CompactTextString(m) } +func (*GetAllMetadataRequest) ProtoMessage() {} +func (*GetAllMetadataRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ad34efc7bbd96e69, []int{1} +} + +func (m *GetAllMetadataRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAllMetadataRequest.Unmarshal(m, b) +} +func (m *GetAllMetadataRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAllMetadataRequest.Marshal(b, m, deterministic) +} +func (m *GetAllMetadataRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAllMetadataRequest.Merge(m, src) +} +func (m *GetAllMetadataRequest) XXX_Size() int { + return xxx_messageInfo_GetAllMetadataRequest.Size(m) +} +func (m *GetAllMetadataRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetAllMetadataRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAllMetadataRequest proto.InternalMessageInfo + +func (m *GetAllMetadataRequest) GetSourceHost() string { + if m != nil { + return m.SourceHost + } + return "" +} + +type IdRequest struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *IdRequest) Reset() { *m = IdRequest{} } +func (m *IdRequest) String() string { return proto.CompactTextString(m) } +func (*IdRequest) ProtoMessage() {} +func (*IdRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ad34efc7bbd96e69, []int{2} +} + +func (m *IdRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_IdRequest.Unmarshal(m, b) +} +func (m *IdRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_IdRequest.Marshal(b, m, deterministic) +} +func (m *IdRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_IdRequest.Merge(m, src) +} +func (m *IdRequest) XXX_Size() int { + return xxx_messageInfo_IdRequest.Size(m) +} +func (m *IdRequest) XXX_DiscardUnknown() { + xxx_messageInfo_IdRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_IdRequest proto.InternalMessageInfo + +func (m *IdRequest) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +type UpdateRequest struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Credential *CredentialRequest `protobuf:"bytes,2,opt,name=credential,proto3" json:"credential,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateRequest) Reset() { *m = UpdateRequest{} } +func (m *UpdateRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateRequest) ProtoMessage() {} +func (*UpdateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ad34efc7bbd96e69, []int{3} +} + +func (m *UpdateRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateRequest.Unmarshal(m, b) +} +func (m *UpdateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateRequest.Marshal(b, m, deterministic) +} +func (m *UpdateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateRequest.Merge(m, src) +} +func (m *UpdateRequest) XXX_Size() int { + return xxx_messageInfo_UpdateRequest.Size(m) +} +func (m *UpdateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateRequest proto.InternalMessageInfo + +func (m *UpdateRequest) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *UpdateRequest) GetCredential() *CredentialRequest { + if m != nil { + return m.Credential + } + return nil +} + +type Metadata struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + CreatedAt *timestamp.Timestamp `protobuf:"bytes,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamp.Timestamp `protobuf:"bytes,3,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + Primary string `protobuf:"bytes,4,opt,name=primary,proto3" json:"primary,omitempty"` + SourceHost string `protobuf:"bytes,5,opt,name=source_host,json=sourceHost,proto3" json:"source_host,omitempty"` + LoginUrl string `protobuf:"bytes,6,opt,name=login_url,json=loginUrl,proto3" json:"login_url,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Metadata) Reset() { *m = Metadata{} } +func (m *Metadata) String() string { return proto.CompactTextString(m) } +func (*Metadata) ProtoMessage() {} +func (*Metadata) Descriptor() ([]byte, []int) { + return fileDescriptor_ad34efc7bbd96e69, []int{4} +} + +func (m *Metadata) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Metadata.Unmarshal(m, b) +} +func (m *Metadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Metadata.Marshal(b, m, deterministic) +} +func (m *Metadata) XXX_Merge(src proto.Message) { + xxx_messageInfo_Metadata.Merge(m, src) +} +func (m *Metadata) XXX_Size() int { + return xxx_messageInfo_Metadata.Size(m) +} +func (m *Metadata) XXX_DiscardUnknown() { + xxx_messageInfo_Metadata.DiscardUnknown(m) +} + +var xxx_messageInfo_Metadata proto.InternalMessageInfo + +func (m *Metadata) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Metadata) GetCreatedAt() *timestamp.Timestamp { + if m != nil { + return m.CreatedAt + } + return nil +} + +func (m *Metadata) GetUpdatedAt() *timestamp.Timestamp { + if m != nil { + return m.UpdatedAt + } + return nil +} + +func (m *Metadata) GetPrimary() string { + if m != nil { + return m.Primary + } + return "" +} + +func (m *Metadata) GetSourceHost() string { + if m != nil { + return m.SourceHost + } + return "" +} + +func (m *Metadata) GetLoginUrl() string { + if m != nil { + return m.LoginUrl + } + return "" +} + +type Credential struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + CreatedAt *timestamp.Timestamp `protobuf:"bytes,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamp.Timestamp `protobuf:"bytes,3,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + Primary string `protobuf:"bytes,4,opt,name=primary,proto3" json:"primary,omitempty"` + Username string `protobuf:"bytes,5,opt,name=username,proto3" json:"username,omitempty"` + Email string `protobuf:"bytes,6,opt,name=email,proto3" json:"email,omitempty"` + Password string `protobuf:"bytes,7,opt,name=password,proto3" json:"password,omitempty"` + SourceHost string `protobuf:"bytes,8,opt,name=source_host,json=sourceHost,proto3" json:"source_host,omitempty"` + LoginUrl string `protobuf:"bytes,9,opt,name=login_url,json=loginUrl,proto3" json:"login_url,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Credential) Reset() { *m = Credential{} } +func (m *Credential) String() string { return proto.CompactTextString(m) } +func (*Credential) ProtoMessage() {} +func (*Credential) Descriptor() ([]byte, []int) { + return fileDescriptor_ad34efc7bbd96e69, []int{5} +} + +func (m *Credential) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Credential.Unmarshal(m, b) +} +func (m *Credential) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Credential.Marshal(b, m, deterministic) +} +func (m *Credential) XXX_Merge(src proto.Message) { + xxx_messageInfo_Credential.Merge(m, src) +} +func (m *Credential) XXX_Size() int { + return xxx_messageInfo_Credential.Size(m) +} +func (m *Credential) XXX_DiscardUnknown() { + xxx_messageInfo_Credential.DiscardUnknown(m) +} + +var xxx_messageInfo_Credential proto.InternalMessageInfo + +func (m *Credential) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Credential) GetCreatedAt() *timestamp.Timestamp { + if m != nil { + return m.CreatedAt + } + return nil +} + +func (m *Credential) GetUpdatedAt() *timestamp.Timestamp { + if m != nil { + return m.UpdatedAt + } + return nil +} + +func (m *Credential) GetPrimary() string { + if m != nil { + return m.Primary + } + return "" +} + +func (m *Credential) GetUsername() string { + if m != nil { + return m.Username + } + return "" +} + +func (m *Credential) GetEmail() string { + if m != nil { + return m.Email + } + return "" +} + +func (m *Credential) GetPassword() string { + if m != nil { + return m.Password + } + return "" +} + +func (m *Credential) GetSourceHost() string { + if m != nil { + return m.SourceHost + } + return "" +} + +func (m *Credential) GetLoginUrl() string { + if m != nil { + return m.LoginUrl + } + return "" +} + +type CredentialRequest struct { + Primary string `protobuf:"bytes,1,opt,name=primary,proto3" json:"primary,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` + Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"` + SourceHost string `protobuf:"bytes,5,opt,name=source_host,json=sourceHost,proto3" json:"source_host,omitempty"` + LoginUrl string `protobuf:"bytes,6,opt,name=login_url,json=loginUrl,proto3" json:"login_url,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CredentialRequest) Reset() { *m = CredentialRequest{} } +func (m *CredentialRequest) String() string { return proto.CompactTextString(m) } +func (*CredentialRequest) ProtoMessage() {} +func (*CredentialRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ad34efc7bbd96e69, []int{6} +} + +func (m *CredentialRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CredentialRequest.Unmarshal(m, b) +} +func (m *CredentialRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CredentialRequest.Marshal(b, m, deterministic) +} +func (m *CredentialRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CredentialRequest.Merge(m, src) +} +func (m *CredentialRequest) XXX_Size() int { + return xxx_messageInfo_CredentialRequest.Size(m) +} +func (m *CredentialRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CredentialRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CredentialRequest proto.InternalMessageInfo + +func (m *CredentialRequest) GetPrimary() string { + if m != nil { + return m.Primary + } + return "" +} + +func (m *CredentialRequest) GetUsername() string { + if m != nil { + return m.Username + } + return "" +} + +func (m *CredentialRequest) GetEmail() string { + if m != nil { + return m.Email + } + return "" +} + +func (m *CredentialRequest) GetPassword() string { + if m != nil { + return m.Password + } + return "" +} + +func (m *CredentialRequest) GetSourceHost() string { + if m != nil { + return m.SourceHost + } + return "" +} + +func (m *CredentialRequest) GetLoginUrl() string { + if m != nil { + return m.LoginUrl + } + return "" +} + +func init() { + proto.RegisterType((*DeleteResponse)(nil), "selfpass.credentials.DeleteResponse") + proto.RegisterType((*GetAllMetadataRequest)(nil), "selfpass.credentials.GetAllMetadataRequest") + proto.RegisterType((*IdRequest)(nil), "selfpass.credentials.IdRequest") + proto.RegisterType((*UpdateRequest)(nil), "selfpass.credentials.UpdateRequest") + proto.RegisterType((*Metadata)(nil), "selfpass.credentials.Metadata") + proto.RegisterType((*Credential)(nil), "selfpass.credentials.Credential") + proto.RegisterType((*CredentialRequest)(nil), "selfpass.credentials.CredentialRequest") +} + +func init() { proto.RegisterFile("credentials/protobuf/service.proto", fileDescriptor_ad34efc7bbd96e69) } + +var fileDescriptor_ad34efc7bbd96e69 = []byte{ + // 493 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x54, 0x5d, 0x6b, 0xd4, 0x40, + 0x14, 0x25, 0xd9, 0x76, 0x9b, 0xdc, 0xe2, 0x82, 0x43, 0x85, 0x90, 0x82, 0xbb, 0x44, 0xc1, 0xa2, + 0x90, 0x4a, 0x7d, 0xd1, 0xc7, 0xb5, 0xc2, 0xd6, 0x07, 0x11, 0x52, 0xfb, 0xe2, 0xcb, 0x32, 0x4d, + 0x6e, 0xb7, 0x81, 0xc9, 0x4e, 0x9c, 0x99, 0x28, 0xfe, 0x30, 0x41, 0xff, 0x91, 0x3f, 0x43, 0x32, + 0xc9, 0xec, 0x47, 0x3a, 0xdb, 0x5d, 0xdf, 0xfa, 0x96, 0x3b, 0x39, 0x67, 0xe6, 0xdc, 0x33, 0x73, + 0x2e, 0x44, 0xa9, 0xc0, 0x0c, 0xe7, 0x2a, 0xa7, 0x4c, 0x9e, 0x96, 0x82, 0x2b, 0x7e, 0x5d, 0xdd, + 0x9c, 0x4a, 0x14, 0xdf, 0xf3, 0x14, 0x63, 0xbd, 0x40, 0x8e, 0x24, 0xb2, 0x9b, 0x92, 0x4a, 0x19, + 0xaf, 0x80, 0xc3, 0xe1, 0x8c, 0xf3, 0x19, 0xc3, 0x25, 0x49, 0xe5, 0x05, 0x4a, 0x45, 0x8b, 0xb2, + 0xa1, 0x45, 0x2f, 0x61, 0xf0, 0x01, 0x19, 0x2a, 0x4c, 0x50, 0x96, 0x7c, 0x2e, 0x91, 0x04, 0x70, + 0x20, 0xab, 0x34, 0x45, 0x29, 0x03, 0x67, 0xe4, 0x9c, 0x78, 0x89, 0x29, 0xa3, 0xb7, 0xf0, 0x64, + 0x82, 0x6a, 0xcc, 0xd8, 0x27, 0x54, 0x34, 0xa3, 0x8a, 0x26, 0xf8, 0xad, 0x42, 0xa9, 0xc8, 0x10, + 0x0e, 0x25, 0xaf, 0x44, 0x8a, 0xd3, 0x5b, 0x2e, 0x95, 0xa6, 0xf9, 0x09, 0x34, 0x4b, 0x17, 0x5c, + 0xaa, 0xe8, 0x18, 0xfc, 0x8f, 0x99, 0x41, 0x0f, 0xc0, 0xcd, 0xb3, 0x16, 0xe4, 0xe6, 0x59, 0x74, + 0x0b, 0x8f, 0xae, 0xca, 0x8c, 0xd6, 0x12, 0xac, 0x00, 0x32, 0x01, 0x58, 0xf6, 0x14, 0xb8, 0x23, + 0xe7, 0xe4, 0xf0, 0xec, 0x45, 0x6c, 0xeb, 0x37, 0x3e, 0x5f, 0x7c, 0xb7, 0x9b, 0x25, 0x2b, 0xd4, + 0xe8, 0xaf, 0x03, 0x9e, 0xd1, 0x7e, 0xe7, 0x94, 0x77, 0xfa, 0x14, 0xaa, 0x30, 0x9b, 0x52, 0xd5, + 0x9e, 0x12, 0xc6, 0x8d, 0x7f, 0xb1, 0xf1, 0x2f, 0xfe, 0x62, 0xfc, 0x4b, 0xfc, 0x16, 0x3d, 0x56, + 0x35, 0xb5, 0xd2, 0x1d, 0x68, 0x6a, 0x6f, 0x3b, 0xb5, 0x45, 0x8f, 0x55, 0xed, 0x76, 0x29, 0xf2, + 0x82, 0x8a, 0x9f, 0xc1, 0x9e, 0x96, 0x62, 0xca, 0xae, 0xa9, 0xfb, 0x5d, 0x53, 0xc9, 0x31, 0xf8, + 0x8c, 0xcf, 0xf2, 0xf9, 0xb4, 0x12, 0x2c, 0xe8, 0xeb, 0xdf, 0x9e, 0x5e, 0xb8, 0x12, 0x2c, 0xfa, + 0xed, 0x02, 0x2c, 0xcd, 0x78, 0xf0, 0xcd, 0x86, 0xe0, 0x55, 0x12, 0xc5, 0x9c, 0x16, 0xd8, 0x76, + 0xba, 0xa8, 0xc9, 0x11, 0xec, 0x63, 0x41, 0x73, 0xd3, 0x63, 0x53, 0xd4, 0x8c, 0xfa, 0xf6, 0x7f, + 0x70, 0x91, 0x05, 0x07, 0x0d, 0xc3, 0xd4, 0x5d, 0xeb, 0xbc, 0xfb, 0xad, 0xf3, 0x3b, 0xd6, 0xfd, + 0x71, 0xe0, 0xf1, 0x9d, 0x77, 0xb4, 0xaa, 0xdd, 0xd9, 0xac, 0xdd, 0xdd, 0xa4, 0xbd, 0xb7, 0x49, + 0xfb, 0xde, 0xfd, 0xda, 0xff, 0xef, 0xda, 0xcf, 0x7e, 0xf5, 0x56, 0xb5, 0x5f, 0x36, 0x13, 0x82, + 0x4c, 0x61, 0xb0, 0x1e, 0x5c, 0xf2, 0xca, 0x1e, 0x1f, 0x6b, 0xbc, 0xc3, 0xa7, 0x76, 0xb0, 0x81, + 0xbd, 0x76, 0xc8, 0x05, 0xf4, 0x26, 0xa8, 0xc8, 0xd0, 0x0e, 0x5c, 0x44, 0x3f, 0x1c, 0x6d, 0x4b, + 0x2d, 0xb9, 0x84, 0xfe, 0xb9, 0x7e, 0x6a, 0x64, 0xd7, 0x84, 0xef, 0xb0, 0xe9, 0x67, 0xe8, 0x37, + 0x13, 0x86, 0x3c, 0xb3, 0x63, 0xd7, 0xe6, 0xcf, 0x6e, 0x1b, 0x36, 0x53, 0x73, 0x7b, 0xcb, 0xcf, + 0xed, 0x80, 0xf5, 0xa1, 0xfb, 0x1e, 0xbe, 0x7a, 0x26, 0x3a, 0xd7, 0x7d, 0xfd, 0xf5, 0xe6, 0x5f, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x8f, 0xb2, 0x15, 0xd8, 0xf6, 0x05, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// CredentialServiceClient is the client API for CredentialService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type CredentialServiceClient interface { + GetAllMetadata(ctx context.Context, in *GetAllMetadataRequest, opts ...grpc.CallOption) (CredentialService_GetAllMetadataClient, error) + Get(ctx context.Context, in *IdRequest, opts ...grpc.CallOption) (*Credential, error) + Create(ctx context.Context, in *CredentialRequest, opts ...grpc.CallOption) (*Credential, error) + Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*Credential, error) + Delete(ctx context.Context, in *IdRequest, opts ...grpc.CallOption) (*DeleteResponse, error) +} + +type credentialServiceClient struct { + cc *grpc.ClientConn +} + +func NewCredentialServiceClient(cc *grpc.ClientConn) CredentialServiceClient { + return &credentialServiceClient{cc} +} + +func (c *credentialServiceClient) GetAllMetadata(ctx context.Context, in *GetAllMetadataRequest, opts ...grpc.CallOption) (CredentialService_GetAllMetadataClient, error) { + stream, err := c.cc.NewStream(ctx, &_CredentialService_serviceDesc.Streams[0], "/selfpass.credentials.CredentialService/GetAllMetadata", opts...) + if err != nil { + return nil, err + } + x := &credentialServiceGetAllMetadataClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type CredentialService_GetAllMetadataClient interface { + Recv() (*Metadata, error) + grpc.ClientStream +} + +type credentialServiceGetAllMetadataClient struct { + grpc.ClientStream +} + +func (x *credentialServiceGetAllMetadataClient) Recv() (*Metadata, error) { + m := new(Metadata) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *credentialServiceClient) Get(ctx context.Context, in *IdRequest, opts ...grpc.CallOption) (*Credential, error) { + out := new(Credential) + err := c.cc.Invoke(ctx, "/selfpass.credentials.CredentialService/Get", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialServiceClient) Create(ctx context.Context, in *CredentialRequest, opts ...grpc.CallOption) (*Credential, error) { + out := new(Credential) + err := c.cc.Invoke(ctx, "/selfpass.credentials.CredentialService/Create", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialServiceClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*Credential, error) { + out := new(Credential) + err := c.cc.Invoke(ctx, "/selfpass.credentials.CredentialService/Update", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialServiceClient) Delete(ctx context.Context, in *IdRequest, opts ...grpc.CallOption) (*DeleteResponse, error) { + out := new(DeleteResponse) + err := c.cc.Invoke(ctx, "/selfpass.credentials.CredentialService/Delete", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CredentialServiceServer is the server API for CredentialService service. +type CredentialServiceServer interface { + GetAllMetadata(*GetAllMetadataRequest, CredentialService_GetAllMetadataServer) error + Get(context.Context, *IdRequest) (*Credential, error) + Create(context.Context, *CredentialRequest) (*Credential, error) + Update(context.Context, *UpdateRequest) (*Credential, error) + Delete(context.Context, *IdRequest) (*DeleteResponse, error) +} + +// UnimplementedCredentialServiceServer can be embedded to have forward compatible implementations. +type UnimplementedCredentialServiceServer struct { +} + +func (*UnimplementedCredentialServiceServer) GetAllMetadata(req *GetAllMetadataRequest, srv CredentialService_GetAllMetadataServer) error { + return status.Errorf(codes.Unimplemented, "method GetAllMetadata not implemented") +} +func (*UnimplementedCredentialServiceServer) Get(ctx context.Context, req *IdRequest) (*Credential, error) { + return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") +} +func (*UnimplementedCredentialServiceServer) Create(ctx context.Context, req *CredentialRequest) (*Credential, error) { + return nil, status.Errorf(codes.Unimplemented, "method Create not implemented") +} +func (*UnimplementedCredentialServiceServer) Update(ctx context.Context, req *UpdateRequest) (*Credential, error) { + return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") +} +func (*UnimplementedCredentialServiceServer) Delete(ctx context.Context, req *IdRequest) (*DeleteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") +} + +func RegisterCredentialServiceServer(s *grpc.Server, srv CredentialServiceServer) { + s.RegisterService(&_CredentialService_serviceDesc, srv) +} + +func _CredentialService_GetAllMetadata_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetAllMetadataRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(CredentialServiceServer).GetAllMetadata(m, &credentialServiceGetAllMetadataServer{stream}) +} + +type CredentialService_GetAllMetadataServer interface { + Send(*Metadata) error + grpc.ServerStream +} + +type credentialServiceGetAllMetadataServer struct { + grpc.ServerStream +} + +func (x *credentialServiceGetAllMetadataServer) Send(m *Metadata) error { + return x.ServerStream.SendMsg(m) +} + +func _CredentialService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialServiceServer).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/selfpass.credentials.CredentialService/Get", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialServiceServer).Get(ctx, req.(*IdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CredentialRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialServiceServer).Create(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/selfpass.credentials.CredentialService/Create", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialServiceServer).Create(ctx, req.(*CredentialRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialService_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialServiceServer).Update(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/selfpass.credentials.CredentialService/Update", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialServiceServer).Update(ctx, req.(*UpdateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialServiceServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/selfpass.credentials.CredentialService/Delete", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialServiceServer).Delete(ctx, req.(*IdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _CredentialService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "selfpass.credentials.CredentialService", + HandlerType: (*CredentialServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Get", + Handler: _CredentialService_Get_Handler, + }, + { + MethodName: "Create", + Handler: _CredentialService_Create_Handler, + }, + { + MethodName: "Update", + Handler: _CredentialService_Update_Handler, + }, + { + MethodName: "Delete", + Handler: _CredentialService_Delete_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "GetAllMetadata", + Handler: _CredentialService_GetAllMetadata_Handler, + ServerStreams: true, + }, + }, + Metadata: "credentials/protobuf/service.proto", +} diff --git a/credentials/protobuf/service.proto b/credentials/protobuf/service.proto new file mode 100644 index 0000000..4e91f59 --- /dev/null +++ b/credentials/protobuf/service.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; + +package selfpass.credentials; + +option go_package = "protobuf"; + +import "google/protobuf/timestamp.proto"; + +service CredentialService { + rpc GetAllMetadata (GetAllMetadataRequest) returns (stream Metadata); + rpc Get (IdRequest) returns (Credential); + rpc Create (CredentialRequest) returns (Credential); + rpc Update (UpdateRequest) returns (Credential); + rpc Delete (IdRequest) returns (DeleteResponse); +} + +message DeleteResponse { + bool success = 1; +} + +message GetAllMetadataRequest { + string source_host = 1; +} + +message IdRequest { + string id = 1; +} + +message UpdateRequest { + string id = 1; + CredentialRequest credential = 2; +} + +message Metadata { + string id = 1; + google.protobuf.Timestamp created_at = 2; + google.protobuf.Timestamp updated_at = 3; + string primary = 4; + string source_host = 5; + string login_url = 6; +} + +message Credential { + string id = 1; + google.protobuf.Timestamp created_at = 2; + google.protobuf.Timestamp updated_at = 3; + string primary = 4; + string username = 5; + string email = 6; + string password = 7; + string source_host = 8; + string login_url = 9; +} + +message CredentialRequest { + string primary = 1; + string username = 2; + string email = 3; + string password = 4; + string source_host = 5; + string login_url = 6; +} diff --git a/credentials/repositories/dynamodb.go b/credentials/repositories/dynamodb.go new file mode 100644 index 0000000..6973613 --- /dev/null +++ b/credentials/repositories/dynamodb.go @@ -0,0 +1,118 @@ +package repositories + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws/external" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/dynamodbattribute" + "github.com/mitchell/selfpass/credentials/types" +) + +// NewDynamoTable TODO +func NewDynamoTable(name string) DynamoTable { + cfg, err := external.LoadDefaultAWSConfig() + if err != nil { + panic(err.Error()) + } + return DynamoTable{ + name: name, + svc: dynamodb.New(cfg), + } +} + +// DynamoTable TODO +type DynamoTable struct { + name string + svc *dynamodb.DynamoDB +} + +// GetAllMetadata TODO +func (t DynamoTable) GetAllMetadata(ctx context.Context, sourceService string, errch chan<- error) (output <-chan types.Metadata) { + mdch := make(chan types.Metadata, 1) + in := &dynamodb.ScanInput{TableName: &t.name} + + if sourceService != "" { + filterExpr := "SourceHost = :sh" + in.FilterExpression = &filterExpr + in.ExpressionAttributeValues = map[string]dynamodb.AttributeValue{ + ":sh": {S: &sourceService}, + } + } + + req := t.svc.ScanRequest(in) + + go func() { + defer close(mdch) + + pgr := req.Paginate() + for pgr.Next() { + mds := []types.Metadata{} + out := pgr.CurrentPage() + if err := dynamodbattribute.UnmarshalListOfMaps(out.Items, &mds); err != nil { + errch <- err + return + } + + for _, md := range mds { + mdch <- md + } + } + + if err := pgr.Err(); err != nil { + errch <- err + return + } + }() + + return mdch +} + +// Get TODO +func (t DynamoTable) Get(ctx context.Context, id string) (output types.Credential, err error) { + req := t.svc.GetItemRequest(&dynamodb.GetItemInput{ + TableName: &t.name, + Key: map[string]dynamodb.AttributeValue{ + "ID": {S: &id}, + }, + }) + + out, err := req.Send() + if err != nil { + return output, err + } + + err = dynamodbattribute.UnmarshalMap(out.Item, &output) + return output, err +} + +// Put TODO +func (t DynamoTable) Put(ctx context.Context, c types.Credential) (err error) { + item, err := dynamodbattribute.MarshalMap(c) + if err != nil { + return err + } + + req := t.svc.PutItemRequest(&dynamodb.PutItemInput{ + TableName: &t.name, + Item: item, + }) + req.SetContext(ctx) + + _, err = req.Send() + + return err +} + +// Delete TODO +func (t DynamoTable) Delete(ctx context.Context, id string) (err error) { + req := t.svc.DeleteItemRequest(&dynamodb.DeleteItemInput{ + TableName: &t.name, + Key: map[string]dynamodb.AttributeValue{ + "ID": {S: &id}, + }, + }) + + _, err = req.Send() + return err +} diff --git a/credentials/service/service.go b/credentials/service/service.go new file mode 100644 index 0000000..8b55029 --- /dev/null +++ b/credentials/service/service.go @@ -0,0 +1,84 @@ +package service + +import ( + "context" + "time" + + "github.com/google/uuid" + + "github.com/mitchell/selfpass/credentials/types" +) + +// NewCredentials TODO +func NewCredentials(repo types.CredentialRepo) Credentials { + return Credentials{ + repo: repo, + } +} + +// Credentials TODO +type Credentials struct { + repo types.CredentialRepo +} + +// GetAllMetadata TODO +func (svc Credentials) GetAllMetadata(ctx context.Context, sourceService string) (output <-chan types.Metadata, errch chan error) { + errch = make(chan error, 1) + output = svc.repo.GetAllMetadata(ctx, sourceService, errch) + return output, errch +} + +// Get TODO +func (svc Credentials) Get(ctx context.Context, id string) (output types.Credential, err error) { + return svc.repo.Get(nil, id) +} + +// Create TODO +func (svc Credentials) Create(ctx context.Context, ci types.CredentialInput) (output types.Credential, err error) { + if err = validateCreate(ci); err != nil { + return output, err + } + + var c types.Credential + + c.ID = "cred-" + uuid.New().String() + c.CreatedAt = time.Now() + c.UpdatedAt = time.Now() + c.Primary = ci.Primary + c.LoginURL = ci.LoginURL + c.SourceHost = ci.SourceHost + c.Username = ci.Username + c.Email = ci.Email + c.Password = ci.Password + + err = svc.repo.Put(ctx, c) + + return c, err +} + +func validateCreate(c types.CredentialInput) (err error) { + return err +} + +// Update TODO +func (svc Credentials) Update(ctx context.Context, id string, ci types.CredentialInput) (output types.Credential, err error) { + c, err := svc.repo.Get(ctx, id) + if err != nil { + return output, err + } + + c.UpdatedAt = time.Now() + c.Primary = ci.Primary + c.LoginURL = ci.LoginURL + c.SourceHost = ci.SourceHost + c.Password = ci.Password + c.Email = ci.Email + c.Username = ci.Username + + return c, svc.repo.Put(ctx, c) +} + +// Delete TODO +func (svc Credentials) Delete(ctx context.Context, id string) (err error) { + return svc.repo.Delete(nil, id) +} diff --git a/credentials/transport/encoding.go b/credentials/transport/encoding.go new file mode 100644 index 0000000..477723f --- /dev/null +++ b/credentials/transport/encoding.go @@ -0,0 +1,127 @@ +package transport + +import ( + "context" + + "github.com/golang/protobuf/ptypes" + "github.com/mitchell/selfpass/credentials/endpoints" + "github.com/mitchell/selfpass/credentials/protobuf" + "github.com/mitchell/selfpass/credentials/types" +) + +func decodeGetAllMetadataRequest(ctx context.Context, request interface{}) (interface{}, error) { + r := request.(protobuf.GetAllMetadataRequest) + return endpoints.GetAllMetadataRequest{ + SourceHost: r.SourceHost, + }, nil +} + +func encodeMetadataStreamResponse(ctx context.Context, response interface{}) (interface{}, error) { + r := response.(endpoints.MetadataStream) + pbmdch := make(chan protobuf.Metadata, 1) + + go func() { + defer close(pbmdch) + + for md := range r.Metadata { + createdAt, err := ptypes.TimestampProto(md.CreatedAt) + if err != nil { + r.Errors <- err + return + } + + updatedAt, err := ptypes.TimestampProto(md.UpdatedAt) + if err != nil { + r.Errors <- err + return + } + + pbmdch <- protobuf.Metadata{ + Id: md.ID, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + SourceHost: md.SourceHost, + Primary: md.Primary, + LoginUrl: md.LoginURL, + } + } + }() + + return protobufMetadataStream{ + Metadata: pbmdch, + Errors: r.Errors, + }, nil +} + +type protobufMetadataStream struct { + Metadata <-chan protobuf.Metadata + Errors chan error +} + +func decodeCredentialRequest(ctx context.Context, request interface{}) (interface{}, error) { + r := request.(protobuf.CredentialRequest) + return types.CredentialInput{ + MetadataInput: types.MetadataInput{ + Primary: r.Primary, + LoginURL: r.LoginUrl, + SourceHost: r.SourceHost, + }, + Username: r.Username, + Email: r.Email, + Password: r.Password, + }, nil +} + +func encodeCredentialResponse(ctx context.Context, response interface{}) (interface{}, error) { + r := response.(types.Credential) + + createdAt, err := ptypes.TimestampProto(r.CreatedAt) + if err != nil { + return nil, err + } + + updatedAt, err := ptypes.TimestampProto(r.UpdatedAt) + if err != nil { + return nil, err + } + + return protobuf.Credential{ + Id: r.ID, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + Primary: r.Primary, + SourceHost: r.SourceHost, + LoginUrl: r.LoginURL, + Username: r.Username, + Email: r.Email, + Password: r.Password, + }, nil +} + +func decodeUpdateRequest(ctx context.Context, request interface{}) (interface{}, error) { + r := request.(protobuf.UpdateRequest) + return endpoints.UpdateRequest{ + ID: r.Id, + Credential: types.CredentialInput{ + MetadataInput: types.MetadataInput{ + Primary: r.Credential.Primary, + SourceHost: r.Credential.SourceHost, + LoginURL: r.Credential.LoginUrl, + }, + Username: r.Credential.Username, + Email: r.Credential.Email, + Password: r.Credential.Password, + }, + }, nil +} + +func decodeIdRequest(ctx context.Context, request interface{}) (interface{}, error) { + r := request.(protobuf.IdRequest) + return endpoints.IDRequest{ + ID: r.Id, + }, nil +} + +func noOpEncode(ctx context.Context, request interface{}) (interface{}, error) { + return nil, nil +} diff --git a/credentials/transport/grpc_server.go b/credentials/transport/grpc_server.go new file mode 100644 index 0000000..fd196ec --- /dev/null +++ b/credentials/transport/grpc_server.go @@ -0,0 +1,146 @@ +package transport + +import ( + "context" + "fmt" + "strings" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/transport/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/mitchell/selfpass/credentials/endpoints" + "github.com/mitchell/selfpass/credentials/protobuf" + "github.com/mitchell/selfpass/credentials/types" +) + +// NewGRPCServer TODO +func NewGRPCServer(svc types.Service, logger log.Logger) GRPCServer { + return GRPCServer{ + getAllMetadata: grpc.NewServer( + endpoints.MakeGetAllMetadataEndpoint(svc), + decodeGetAllMetadataRequest, + encodeMetadataStreamResponse, + grpc.ServerErrorLogger(logger), + ), + create: grpc.NewServer( + endpoints.MakeCreateEndpoint(svc), + decodeCredentialRequest, + encodeCredentialResponse, + grpc.ServerErrorLogger(logger), + ), + update: grpc.NewServer( + endpoints.MakeUpdateEndpoint(svc), + decodeUpdateRequest, + encodeCredentialResponse, + grpc.ServerErrorLogger(logger), + ), + delete: grpc.NewServer( + endpoints.MakeDeleteEndpoint(svc), + decodeIdRequest, + noOpEncode, + grpc.ServerErrorLogger(logger), + ), + } +} + +// GRPCServer TODO +type GRPCServer struct { + getAllMetadata *grpc.Server + create *grpc.Server + update *grpc.Server + delete *grpc.Server +} + +// GetAllMetadata TODO +func (s GRPCServer) GetAllMetadata(r *protobuf.GetAllMetadataRequest, srv protobuf.CredentialService_GetAllMetadataServer) (err error) { + defer func() { + err = handlerGRPCError(err) + }() + + var i interface{} + ctx := srv.Context() + + ctx, i, err = s.getAllMetadata.ServeGRPC(ctx, *r) + if err != nil { + return err + } + + mds := i.(protobufMetadataStream) + +receiveLoop: + for { + select { + case <-ctx.Done(): + break receiveLoop + case err = <-mds.Errors: + break receiveLoop + case md, ok := <-mds.Metadata: + if !ok { + break receiveLoop + } + fmt.Println(md) + if err = srv.Send(&md); err != nil { + break receiveLoop + } + } + } + + return err +} + +// Get TODO +func (s GRPCServer) Get(context.Context, *protobuf.IdRequest) (*protobuf.Credential, error) { + panic("implement me") +} + +// Create TODO +func (s GRPCServer) Create(ctx context.Context, ci *protobuf.CredentialRequest) (*protobuf.Credential, error) { + ctx, i, err := s.create.ServeGRPC(ctx, *ci) + if err != nil { + err = handlerGRPCError(err) + return nil, err + } + + c := &protobuf.Credential{} + *c = i.(protobuf.Credential) + return c, nil +} + +// Update TODO +func (s GRPCServer) Update(ctx context.Context, r *protobuf.UpdateRequest) (*protobuf.Credential, error) { + ctx, i, err := s.update.ServeGRPC(ctx, *r) + if err != nil { + err = handlerGRPCError(err) + return nil, err + } + + c := &protobuf.Credential{} + *c = i.(protobuf.Credential) + return c, nil +} + +// Delete TODO +func (s GRPCServer) Delete(ctx context.Context, r *protobuf.IdRequest) (*protobuf.DeleteResponse, error) { + ctx, _, err := s.delete.ServeGRPC(ctx, *r) + if err != nil { + return nil, err + } + + return &protobuf.DeleteResponse{Success: true}, nil +} + +func handlerGRPCError(err error) error { + if err != nil { + switch { + case strings.HasPrefix(err.Error(), types.InvalidArgument): + err = status.Error(codes.InvalidArgument, err.Error()) + case strings.HasPrefix(err.Error(), types.NotFound): + err = status.Error(codes.NotFound, err.Error()) + default: + err = status.Error(codes.Internal, "an internal error has occurred") + } + } + return err +} diff --git a/credentials/types/credential.go b/credentials/types/credential.go new file mode 100644 index 0000000..3ceeda8 --- /dev/null +++ b/credentials/types/credential.go @@ -0,0 +1,36 @@ +package types + +import "time" + +// Credential TODO +type Credential struct { + Metadata + Username string + Email string + Password string +} + +// CredentialInput TODO +type CredentialInput struct { + MetadataInput + Username string + Email string + Password string +} + +// Metadata TODO +type Metadata struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + Primary string + SourceHost string + LoginURL string +} + +// MetadataInput TODO +type MetadataInput struct { + Primary string + SourceHost string + LoginURL string +} diff --git a/credentials/types/error_prefixes.go b/credentials/types/error_prefixes.go new file mode 100644 index 0000000..94bad95 --- /dev/null +++ b/credentials/types/error_prefixes.go @@ -0,0 +1,8 @@ +package types + +// These constants are the prefixes of errors that should be exposed to the client. +// All error encoders should check for them. +const ( + NotFound = "not found:" + InvalidArgument = "invalid argument:" +) diff --git a/credentials/types/interfaces.go b/credentials/types/interfaces.go new file mode 100644 index 0000000..bc62d93 --- /dev/null +++ b/credentials/types/interfaces.go @@ -0,0 +1,20 @@ +package types + +import "context" + +// Service TODO +type Service interface { + GetAllMetadata(ctx context.Context, sourceService string) (output <-chan Metadata, errch chan error) + Get(ctx context.Context, id string) (output Credential, err error) + Create(ctx context.Context, ci CredentialInput) (output Credential, err error) + Update(ctx context.Context, id string, ci CredentialInput) (output Credential, err error) + Delete(ctx context.Context, id string) (err error) +} + +// CredentialRepo TODO +type CredentialRepo interface { + GetAllMetadata(ctx context.Context, sourceService string, errch chan<- error) (output <-chan Metadata) + Get(ctx context.Context, id string) (output Credential, err error) + Put(ctx context.Context, c Credential) (err error) + Delete(ctx context.Context, id string) (err error) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6f22b9b --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/mitchell/selfpass + +require ( + github.com/aws/aws-sdk-go-v2 v0.7.0 + github.com/go-kit/kit v0.8.0 + github.com/go-logfmt/logfmt v0.4.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/golang/protobuf v1.3.1 + github.com/google/uuid v1.1.1 + github.com/stretchr/testify v1.3.0 // indirect + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 // indirect + golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect + google.golang.org/grpc v1.20.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..22fcfef --- /dev/null +++ b/go.sum @@ -0,0 +1,64 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aws/aws-sdk-go-v2 v0.7.0 h1:a5xRI/tBmUFKuAA0SOyEY2P1YhQb+jVOEI9P/7KfrP0= +github.com/aws/aws-sdk-go-v2 v0.7.0/go.mod h1:17MaCZ9g0q5BIMxwzRQeiv8M3c8+W7iuBnlWAEprcxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gucumber/gucumber v0.0.0-20180127021336-7d5c79e832a2/go.mod h1:YbdHRK9ViqwGMS0rtRY+1I6faHvVyyurKPIPwifihxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.20.0 h1:DlsSIrgEBuZAUFJcta2B5i/lzeHHbnfkNFAfFXLVFYQ= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=