mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-23 04:10:28 +00:00
MF-932 - User API keys (#941)
* Add inital Auth implementation Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Extract IssuedAt on transport layer Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Add token type Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Fix Auth service URL in Things service Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Add User Keys revocation check Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Update tests Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Remove unused tracing methods Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Fix Key retrival and parsing Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Remove unused code Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Increase test coverage Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Fix compose files Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Fix typos Simplify tests. Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Fix typos and remove useless comments Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Rename Auth to Authn Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Rename database.go to tracin.go A new name (`tracing.go`) describes better the purpose of the file. Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Increase test coverage Fix typo. Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Increase test coverage Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Remove token from Users service Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Fix identify login keys Rename token parsing method. Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Extract tokenizer to interface Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Remove pointer time Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Use pointer for expiration time in response Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Use uppercase N Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Remove unnecessary email check Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Cleanup unused code and env vars Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Rename tokenizer field Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Use slices and named fields in test cases Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Update AuthN keys naming Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Remove package-lock.json changes Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com> * Remove Secret from issuing request Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
This commit is contained in:
committed by
Nikola Marčetić
parent
bdeb7711ce
commit
9f37927dec
@@ -26,15 +26,24 @@ MF_JAEGER_CONFIGS=5778
|
||||
MF_JAEGER_URL=jaeger:6831
|
||||
|
||||
## Core Services
|
||||
|
||||
### AuthN
|
||||
MF_AUTHN_LOG_LEVEL=debug
|
||||
MF_AUTHN_HTTP_PORT=8189
|
||||
MF_AUTHN_GRPC_PORT=8181
|
||||
MF_AUTHN_DB_PORT=5432
|
||||
MF_AUTHN_DB_USER=mainflux
|
||||
MF_AUTHN_DB_PASS=mainflux
|
||||
MF_AUTHN_DB=authn
|
||||
MF_AUTHN_SECRET=secret
|
||||
|
||||
### Users
|
||||
MF_USERS_LOG_LEVEL=debug
|
||||
MF_USERS_HTTP_PORT=8180
|
||||
MF_USERS_GRPC_PORT=8181
|
||||
MF_USERS_DB_PORT=5432
|
||||
MF_USERS_DB_USER=mainflux
|
||||
MF_USERS_DB_PASS=mainflux
|
||||
MF_USERS_DB=users
|
||||
MF_USERS_SECRET=secret
|
||||
|
||||
### Email utility
|
||||
MF_EMAIL_DRIVER=smtp
|
||||
@@ -47,9 +56,7 @@ MF_EMAIL_FROM_NAME=Example
|
||||
MF_EMAIL_TEMPLATE=email.tmpl
|
||||
|
||||
### Token utility
|
||||
MF_TOKEN_SECRET=some_random_bytes
|
||||
MF_TOKEN_RESET_ENDPOINT=/reset-request
|
||||
MF_TOKEN_DURATION=5
|
||||
|
||||
### Things
|
||||
MF_THINGS_LOG_LEVEL=debug
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
BUILD_DIR = build
|
||||
SERVICES = users things http ws coap lora influxdb-writer influxdb-reader mongodb-writer \
|
||||
mongodb-reader cassandra-writer cassandra-reader postgres-writer postgres-reader cli bootstrap opcua
|
||||
mongodb-reader cassandra-writer cassandra-reader postgres-writer postgres-reader cli \
|
||||
bootstrap opcua authn
|
||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
|
||||
CGO_ENABLED ?= 0
|
||||
|
||||
+385
-124
@@ -1,5 +1,5 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: internal.proto
|
||||
// source: authn.proto
|
||||
|
||||
package mainflux
|
||||
|
||||
@@ -39,7 +39,7 @@ func (m *AccessByKeyReq) Reset() { *m = AccessByKeyReq{} }
|
||||
func (m *AccessByKeyReq) String() string { return proto.CompactTextString(m) }
|
||||
func (*AccessByKeyReq) ProtoMessage() {}
|
||||
func (*AccessByKeyReq) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_41f4a519b878ee3b, []int{0}
|
||||
return fileDescriptor_b40bfba985381dd1, []int{0}
|
||||
}
|
||||
func (m *AccessByKeyReq) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -93,7 +93,7 @@ func (m *ThingID) Reset() { *m = ThingID{} }
|
||||
func (m *ThingID) String() string { return proto.CompactTextString(m) }
|
||||
func (*ThingID) ProtoMessage() {}
|
||||
func (*ThingID) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_41f4a519b878ee3b, []int{1}
|
||||
return fileDescriptor_b40bfba985381dd1, []int{1}
|
||||
}
|
||||
func (m *ThingID) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -141,7 +141,7 @@ func (m *AccessByIDReq) Reset() { *m = AccessByIDReq{} }
|
||||
func (m *AccessByIDReq) String() string { return proto.CompactTextString(m) }
|
||||
func (*AccessByIDReq) ProtoMessage() {}
|
||||
func (*AccessByIDReq) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_41f4a519b878ee3b, []int{2}
|
||||
return fileDescriptor_b40bfba985381dd1, []int{2}
|
||||
}
|
||||
func (m *AccessByIDReq) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -184,6 +184,9 @@ func (m *AccessByIDReq) GetChanID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// If a token is not carrying any information itself, the type
|
||||
// field can be used to determine how to validate the token.
|
||||
// Also, different tokens can be encoded in different ways.
|
||||
type Token struct {
|
||||
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
@@ -195,7 +198,7 @@ func (m *Token) Reset() { *m = Token{} }
|
||||
func (m *Token) String() string { return proto.CompactTextString(m) }
|
||||
func (*Token) ProtoMessage() {}
|
||||
func (*Token) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_41f4a519b878ee3b, []int{3}
|
||||
return fileDescriptor_b40bfba985381dd1, []int{3}
|
||||
}
|
||||
func (m *Token) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -242,7 +245,7 @@ func (m *UserID) Reset() { *m = UserID{} }
|
||||
func (m *UserID) String() string { return proto.CompactTextString(m) }
|
||||
func (*UserID) ProtoMessage() {}
|
||||
func (*UserID) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_41f4a519b878ee3b, []int{4}
|
||||
return fileDescriptor_b40bfba985381dd1, []int{4}
|
||||
}
|
||||
func (m *UserID) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -278,38 +281,97 @@ func (m *UserID) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type IssueReq struct {
|
||||
Issuer string `protobuf:"bytes,1,opt,name=issuer,proto3" json:"issuer,omitempty"`
|
||||
Type uint32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *IssueReq) Reset() { *m = IssueReq{} }
|
||||
func (m *IssueReq) String() string { return proto.CompactTextString(m) }
|
||||
func (*IssueReq) ProtoMessage() {}
|
||||
func (*IssueReq) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_b40bfba985381dd1, []int{5}
|
||||
}
|
||||
func (m *IssueReq) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *IssueReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_IssueReq.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *IssueReq) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_IssueReq.Merge(m, src)
|
||||
}
|
||||
func (m *IssueReq) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *IssueReq) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_IssueReq.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_IssueReq proto.InternalMessageInfo
|
||||
|
||||
func (m *IssueReq) GetIssuer() string {
|
||||
if m != nil {
|
||||
return m.Issuer
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *IssueReq) GetType() uint32 {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*AccessByKeyReq)(nil), "mainflux.AccessByKeyReq")
|
||||
proto.RegisterType((*ThingID)(nil), "mainflux.ThingID")
|
||||
proto.RegisterType((*AccessByIDReq)(nil), "mainflux.AccessByIDReq")
|
||||
proto.RegisterType((*Token)(nil), "mainflux.Token")
|
||||
proto.RegisterType((*UserID)(nil), "mainflux.UserID")
|
||||
proto.RegisterType((*IssueReq)(nil), "mainflux.IssueReq")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("internal.proto", fileDescriptor_41f4a519b878ee3b) }
|
||||
func init() { proto.RegisterFile("authn.proto", fileDescriptor_b40bfba985381dd1) }
|
||||
|
||||
var fileDescriptor_41f4a519b878ee3b = []byte{
|
||||
// 317 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x50, 0x4d, 0x4e, 0xf2, 0x40,
|
||||
0x18, 0xee, 0x7c, 0x09, 0x3f, 0xdf, 0x1b, 0x41, 0x9c, 0x18, 0x6c, 0x30, 0x56, 0x33, 0x2b, 0x57,
|
||||
0x83, 0xc1, 0xbd, 0x04, 0xac, 0x8b, 0xc6, 0x1d, 0xe2, 0x01, 0x4a, 0x7d, 0x29, 0x8d, 0x65, 0x8a,
|
||||
0xed, 0x94, 0xd8, 0x9b, 0x78, 0x22, 0xe3, 0xd2, 0x23, 0x98, 0x7a, 0x11, 0xd3, 0x69, 0x2b, 0x35,
|
||||
0x82, 0xcb, 0xe7, 0x9d, 0x79, 0x7e, 0xa1, 0xed, 0x09, 0x89, 0xa1, 0xb0, 0x7d, 0xbe, 0x0a, 0x03,
|
||||
0x19, 0xd0, 0xe6, 0xd2, 0xf6, 0xc4, 0xdc, 0x8f, 0x9f, 0x7b, 0xc7, 0x6e, 0x10, 0xb8, 0x3e, 0xf6,
|
||||
0xd5, 0x7d, 0x16, 0xcf, 0xfb, 0xb8, 0x5c, 0xc9, 0x24, 0xff, 0xc6, 0xae, 0xa0, 0x3d, 0x72, 0x1c,
|
||||
0x8c, 0xa2, 0x71, 0x72, 0x8b, 0xc9, 0x04, 0x9f, 0xe8, 0x21, 0xd4, 0x64, 0xf0, 0x88, 0x42, 0x27,
|
||||
0x67, 0xe4, 0xfc, 0xff, 0x24, 0x07, 0xb4, 0x0b, 0x75, 0x67, 0x61, 0x0b, 0xcb, 0xd4, 0xff, 0xa9,
|
||||
0x73, 0x81, 0xd8, 0x29, 0x34, 0xa6, 0x0b, 0x4f, 0xb8, 0x96, 0x99, 0x11, 0xd7, 0xb6, 0x1f, 0x63,
|
||||
0x49, 0x54, 0x80, 0x8d, 0xa0, 0x55, 0x1a, 0x58, 0x66, 0xa6, 0xaf, 0x43, 0x43, 0xe6, 0x8c, 0xe2,
|
||||
0x63, 0x09, 0x77, 0x7a, 0x9c, 0x40, 0x6d, 0xaa, 0x42, 0x6c, 0x77, 0x30, 0xa0, 0x7e, 0x1f, 0x61,
|
||||
0xb8, 0x2b, 0xc1, 0xe0, 0x95, 0x40, 0x4b, 0x65, 0x8c, 0xee, 0x30, 0x5c, 0x7b, 0x0e, 0xd2, 0x21,
|
||||
0xb4, 0xaf, 0x6d, 0x51, 0xe9, 0x4d, 0x75, 0x5e, 0xce, 0xc5, 0x7f, 0xce, 0xd1, 0x3b, 0xd8, 0xbc,
|
||||
0x14, 0x45, 0x99, 0x46, 0xc7, 0xd0, 0xaa, 0x08, 0x58, 0x26, 0x3d, 0xfa, 0xcd, 0x57, 0x6d, 0x7b,
|
||||
0x5d, 0x9e, 0xaf, 0xcf, 0xcb, 0xf5, 0xf9, 0x4d, 0xb6, 0x3e, 0xd3, 0xe8, 0x05, 0x34, 0xad, 0x07,
|
||||
0x14, 0xd2, 0x9b, 0x27, 0x74, 0xbf, 0x62, 0x92, 0x35, 0xdd, 0xea, 0x3a, 0x18, 0xc2, 0x5e, 0x56,
|
||||
0xf4, 0xbb, 0x46, 0xff, 0x2f, 0x85, 0xce, 0xe6, 0x90, 0xaf, 0xc3, 0xb4, 0x71, 0xe7, 0x2d, 0x35,
|
||||
0xc8, 0x7b, 0x6a, 0x90, 0x8f, 0xd4, 0x20, 0x2f, 0x9f, 0x86, 0x36, 0xab, 0xab, 0x58, 0x97, 0x5f,
|
||||
0x01, 0x00, 0x00, 0xff, 0xff, 0xc5, 0x0b, 0xb5, 0x3c, 0x3e, 0x02, 0x00, 0x00,
|
||||
var fileDescriptor_b40bfba985381dd1 = []byte{
|
||||
// 363 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x91, 0xc1, 0x4e, 0xc2, 0x40,
|
||||
0x10, 0x86, 0x5b, 0x23, 0x05, 0x47, 0x8b, 0x38, 0x31, 0xd8, 0xd4, 0x58, 0x4d, 0x4f, 0x9e, 0x16,
|
||||
0x83, 0x89, 0x47, 0x0d, 0x58, 0x0f, 0x8d, 0x89, 0x07, 0xc4, 0x07, 0x28, 0x75, 0xa1, 0x8d, 0xb0,
|
||||
0xc5, 0x76, 0x4b, 0xec, 0x9b, 0xf8, 0x44, 0xc6, 0xa3, 0x8f, 0x60, 0xf0, 0x45, 0x4c, 0xb7, 0xdd,
|
||||
0x80, 0x0a, 0xde, 0xf6, 0x9f, 0x9d, 0x99, 0x7f, 0xe6, 0x1b, 0xd8, 0xf6, 0x52, 0x1e, 0x30, 0x32,
|
||||
0x8d, 0x23, 0x1e, 0x61, 0x6d, 0xe2, 0x85, 0x6c, 0x38, 0x4e, 0x5f, 0xcc, 0xc3, 0x51, 0x14, 0x8d,
|
||||
0xc6, 0xb4, 0x25, 0xe2, 0x83, 0x74, 0xd8, 0xa2, 0x93, 0x29, 0xcf, 0x8a, 0x34, 0xfb, 0x12, 0xea,
|
||||
0x1d, 0xdf, 0xa7, 0x49, 0xd2, 0xcd, 0x6e, 0x69, 0xd6, 0xa3, 0xcf, 0xb8, 0x0f, 0x15, 0x1e, 0x3d,
|
||||
0x51, 0x66, 0xa8, 0x27, 0xea, 0xe9, 0x56, 0xaf, 0x10, 0xd8, 0x04, 0xcd, 0x0f, 0x3c, 0xe6, 0x3a,
|
||||
0xc6, 0x86, 0x08, 0x97, 0xca, 0x3e, 0x86, 0x6a, 0x3f, 0x08, 0xd9, 0xc8, 0x75, 0xf2, 0xc2, 0x99,
|
||||
0x37, 0x4e, 0xa9, 0x2c, 0x14, 0xc2, 0xee, 0x80, 0x2e, 0x0d, 0x5c, 0x27, 0xef, 0x6f, 0x40, 0x95,
|
||||
0x17, 0x15, 0x65, 0xa2, 0x94, 0x6b, 0x3d, 0x8e, 0xa0, 0xd2, 0x17, 0x43, 0xac, 0x76, 0xb0, 0x40,
|
||||
0x7b, 0x48, 0x68, 0xbc, 0x76, 0x82, 0x0b, 0xa8, 0xb9, 0x49, 0x92, 0xd2, 0xdc, 0xbc, 0x09, 0x5a,
|
||||
0x98, 0xbf, 0xe3, 0x32, 0xa5, 0x54, 0x88, 0xb0, 0xc9, 0xb3, 0x29, 0x15, 0xc6, 0x7a, 0x4f, 0xbc,
|
||||
0xdb, 0x6f, 0x2a, 0xe8, 0x62, 0xb7, 0xe4, 0x9e, 0xc6, 0xb3, 0xd0, 0xa7, 0x78, 0x05, 0xf5, 0x6b,
|
||||
0x8f, 0x2d, 0xf1, 0x42, 0x83, 0x48, 0xcc, 0xe4, 0x27, 0x46, 0x73, 0x6f, 0xf1, 0x53, 0x02, 0xb2,
|
||||
0x15, 0xec, 0x82, 0xbe, 0xd4, 0xc0, 0x75, 0xf0, 0xe0, 0x6f, 0xbd, 0xa0, 0x64, 0x36, 0x49, 0x71,
|
||||
0x35, 0x22, 0xaf, 0x46, 0x6e, 0xf2, 0xab, 0xd9, 0x0a, 0x9e, 0x41, 0xcd, 0x7d, 0xa4, 0x8c, 0x87,
|
||||
0xc3, 0x0c, 0x77, 0x97, 0x4c, 0x72, 0x42, 0x2b, 0x5d, 0xdb, 0x11, 0xec, 0x74, 0x52, 0x1e, 0xdc,
|
||||
0xc9, 0x35, 0x08, 0x54, 0x04, 0x10, 0xc4, 0x45, 0xb6, 0x24, 0x64, 0xfe, 0x6e, 0x69, 0x2b, 0xd8,
|
||||
0xfa, 0xcf, 0xb1, 0xb1, 0x08, 0x14, 0x57, 0xb0, 0x95, 0x6e, 0xe3, 0x7d, 0x6e, 0xa9, 0x1f, 0x73,
|
||||
0x4b, 0xfd, 0x9c, 0x5b, 0xea, 0xeb, 0x97, 0xa5, 0x0c, 0x34, 0xb1, 0xc6, 0xf9, 0x77, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0xfa, 0x48, 0x68, 0x52, 0xa3, 0x02, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
@@ -461,79 +523,115 @@ var _ThingsService_serviceDesc = grpc.ServiceDesc{
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "internal.proto",
|
||||
Metadata: "authn.proto",
|
||||
}
|
||||
|
||||
// UsersServiceClient is the client API for UsersService service.
|
||||
// AuthNServiceClient is the client API for AuthNService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type UsersServiceClient interface {
|
||||
type AuthNServiceClient interface {
|
||||
Issue(ctx context.Context, in *IssueReq, opts ...grpc.CallOption) (*Token, error)
|
||||
Identify(ctx context.Context, in *Token, opts ...grpc.CallOption) (*UserID, error)
|
||||
}
|
||||
|
||||
type usersServiceClient struct {
|
||||
type authNServiceClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewUsersServiceClient(cc *grpc.ClientConn) UsersServiceClient {
|
||||
return &usersServiceClient{cc}
|
||||
func NewAuthNServiceClient(cc *grpc.ClientConn) AuthNServiceClient {
|
||||
return &authNServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *usersServiceClient) Identify(ctx context.Context, in *Token, opts ...grpc.CallOption) (*UserID, error) {
|
||||
out := new(UserID)
|
||||
err := c.cc.Invoke(ctx, "/mainflux.UsersService/Identify", in, out, opts...)
|
||||
func (c *authNServiceClient) Issue(ctx context.Context, in *IssueReq, opts ...grpc.CallOption) (*Token, error) {
|
||||
out := new(Token)
|
||||
err := c.cc.Invoke(ctx, "/mainflux.AuthNService/Issue", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// UsersServiceServer is the server API for UsersService service.
|
||||
type UsersServiceServer interface {
|
||||
func (c *authNServiceClient) Identify(ctx context.Context, in *Token, opts ...grpc.CallOption) (*UserID, error) {
|
||||
out := new(UserID)
|
||||
err := c.cc.Invoke(ctx, "/mainflux.AuthNService/Identify", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AuthNServiceServer is the server API for AuthNService service.
|
||||
type AuthNServiceServer interface {
|
||||
Issue(context.Context, *IssueReq) (*Token, error)
|
||||
Identify(context.Context, *Token) (*UserID, error)
|
||||
}
|
||||
|
||||
// UnimplementedUsersServiceServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedUsersServiceServer struct {
|
||||
// UnimplementedAuthNServiceServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedAuthNServiceServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedUsersServiceServer) Identify(ctx context.Context, req *Token) (*UserID, error) {
|
||||
func (*UnimplementedAuthNServiceServer) Issue(ctx context.Context, req *IssueReq) (*Token, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Issue not implemented")
|
||||
}
|
||||
func (*UnimplementedAuthNServiceServer) Identify(ctx context.Context, req *Token) (*UserID, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Identify not implemented")
|
||||
}
|
||||
|
||||
func RegisterUsersServiceServer(s *grpc.Server, srv UsersServiceServer) {
|
||||
s.RegisterService(&_UsersService_serviceDesc, srv)
|
||||
func RegisterAuthNServiceServer(s *grpc.Server, srv AuthNServiceServer) {
|
||||
s.RegisterService(&_AuthNService_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _UsersService_Identify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
func _AuthNService_Issue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(IssueReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AuthNServiceServer).Issue(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/mainflux.AuthNService/Issue",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AuthNServiceServer).Issue(ctx, req.(*IssueReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AuthNService_Identify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Token)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(UsersServiceServer).Identify(ctx, in)
|
||||
return srv.(AuthNServiceServer).Identify(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/mainflux.UsersService/Identify",
|
||||
FullMethod: "/mainflux.AuthNService/Identify",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(UsersServiceServer).Identify(ctx, req.(*Token))
|
||||
return srv.(AuthNServiceServer).Identify(ctx, req.(*Token))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _UsersService_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "mainflux.UsersService",
|
||||
HandlerType: (*UsersServiceServer)(nil),
|
||||
var _AuthNService_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "mainflux.AuthNService",
|
||||
HandlerType: (*AuthNServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Issue",
|
||||
Handler: _AuthNService_Issue_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Identify",
|
||||
Handler: _UsersService_Identify_Handler,
|
||||
Handler: _AuthNService_Identify_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "internal.proto",
|
||||
Metadata: "authn.proto",
|
||||
}
|
||||
|
||||
func (m *AccessByKeyReq) Marshal() (dAtA []byte, err error) {
|
||||
@@ -563,14 +661,14 @@ func (m *AccessByKeyReq) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
if len(m.ChanID) > 0 {
|
||||
i -= len(m.ChanID)
|
||||
copy(dAtA[i:], m.ChanID)
|
||||
i = encodeVarintInternal(dAtA, i, uint64(len(m.ChanID)))
|
||||
i = encodeVarintAuthn(dAtA, i, uint64(len(m.ChanID)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
if len(m.Token) > 0 {
|
||||
i -= len(m.Token)
|
||||
copy(dAtA[i:], m.Token)
|
||||
i = encodeVarintInternal(dAtA, i, uint64(len(m.Token)))
|
||||
i = encodeVarintAuthn(dAtA, i, uint64(len(m.Token)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
@@ -604,7 +702,7 @@ func (m *ThingID) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
if len(m.Value) > 0 {
|
||||
i -= len(m.Value)
|
||||
copy(dAtA[i:], m.Value)
|
||||
i = encodeVarintInternal(dAtA, i, uint64(len(m.Value)))
|
||||
i = encodeVarintAuthn(dAtA, i, uint64(len(m.Value)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
@@ -638,14 +736,14 @@ func (m *AccessByIDReq) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
if len(m.ChanID) > 0 {
|
||||
i -= len(m.ChanID)
|
||||
copy(dAtA[i:], m.ChanID)
|
||||
i = encodeVarintInternal(dAtA, i, uint64(len(m.ChanID)))
|
||||
i = encodeVarintAuthn(dAtA, i, uint64(len(m.ChanID)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
if len(m.ThingID) > 0 {
|
||||
i -= len(m.ThingID)
|
||||
copy(dAtA[i:], m.ThingID)
|
||||
i = encodeVarintInternal(dAtA, i, uint64(len(m.ThingID)))
|
||||
i = encodeVarintAuthn(dAtA, i, uint64(len(m.ThingID)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
@@ -679,7 +777,7 @@ func (m *Token) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
if len(m.Value) > 0 {
|
||||
i -= len(m.Value)
|
||||
copy(dAtA[i:], m.Value)
|
||||
i = encodeVarintInternal(dAtA, i, uint64(len(m.Value)))
|
||||
i = encodeVarintAuthn(dAtA, i, uint64(len(m.Value)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
@@ -713,15 +811,54 @@ func (m *UserID) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
if len(m.Value) > 0 {
|
||||
i -= len(m.Value)
|
||||
copy(dAtA[i:], m.Value)
|
||||
i = encodeVarintInternal(dAtA, i, uint64(len(m.Value)))
|
||||
i = encodeVarintAuthn(dAtA, i, uint64(len(m.Value)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintInternal(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovInternal(v)
|
||||
func (m *IssueReq) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *IssueReq) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *IssueReq) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.XXX_unrecognized != nil {
|
||||
i -= len(m.XXX_unrecognized)
|
||||
copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
if m.Type != 0 {
|
||||
i = encodeVarintAuthn(dAtA, i, uint64(m.Type))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
if len(m.Issuer) > 0 {
|
||||
i -= len(m.Issuer)
|
||||
copy(dAtA[i:], m.Issuer)
|
||||
i = encodeVarintAuthn(dAtA, i, uint64(len(m.Issuer)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintAuthn(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovAuthn(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
@@ -739,11 +876,11 @@ func (m *AccessByKeyReq) Size() (n int) {
|
||||
_ = l
|
||||
l = len(m.Token)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovInternal(uint64(l))
|
||||
n += 1 + l + sovAuthn(uint64(l))
|
||||
}
|
||||
l = len(m.ChanID)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovInternal(uint64(l))
|
||||
n += 1 + l + sovAuthn(uint64(l))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
@@ -759,7 +896,7 @@ func (m *ThingID) Size() (n int) {
|
||||
_ = l
|
||||
l = len(m.Value)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovInternal(uint64(l))
|
||||
n += 1 + l + sovAuthn(uint64(l))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
@@ -775,11 +912,11 @@ func (m *AccessByIDReq) Size() (n int) {
|
||||
_ = l
|
||||
l = len(m.ThingID)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovInternal(uint64(l))
|
||||
n += 1 + l + sovAuthn(uint64(l))
|
||||
}
|
||||
l = len(m.ChanID)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovInternal(uint64(l))
|
||||
n += 1 + l + sovAuthn(uint64(l))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
@@ -795,7 +932,7 @@ func (m *Token) Size() (n int) {
|
||||
_ = l
|
||||
l = len(m.Value)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovInternal(uint64(l))
|
||||
n += 1 + l + sovAuthn(uint64(l))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
@@ -811,7 +948,7 @@ func (m *UserID) Size() (n int) {
|
||||
_ = l
|
||||
l = len(m.Value)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovInternal(uint64(l))
|
||||
n += 1 + l + sovAuthn(uint64(l))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
@@ -819,11 +956,30 @@ func (m *UserID) Size() (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func sovInternal(x uint64) (n int) {
|
||||
func (m *IssueReq) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.Issuer)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovAuthn(uint64(l))
|
||||
}
|
||||
if m.Type != 0 {
|
||||
n += 1 + sovAuthn(uint64(m.Type))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovAuthn(x uint64) (n int) {
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozInternal(x uint64) (n int) {
|
||||
return sovInternal(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
func sozAuthn(x uint64) (n int) {
|
||||
return sovAuthn(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *AccessByKeyReq) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
@@ -833,7 +989,7 @@ func (m *AccessByKeyReq) Unmarshal(dAtA []byte) error {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -861,7 +1017,7 @@ func (m *AccessByKeyReq) Unmarshal(dAtA []byte) error {
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -875,11 +1031,11 @@ func (m *AccessByKeyReq) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -893,7 +1049,7 @@ func (m *AccessByKeyReq) Unmarshal(dAtA []byte) error {
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -907,11 +1063,11 @@ func (m *AccessByKeyReq) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -920,15 +1076,15 @@ func (m *AccessByKeyReq) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipInternal(dAtA[iNdEx:])
|
||||
skippy, err := skipAuthn(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -951,7 +1107,7 @@ func (m *ThingID) Unmarshal(dAtA []byte) error {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -979,7 +1135,7 @@ func (m *ThingID) Unmarshal(dAtA []byte) error {
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -993,11 +1149,11 @@ func (m *ThingID) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1006,15 +1162,15 @@ func (m *ThingID) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipInternal(dAtA[iNdEx:])
|
||||
skippy, err := skipAuthn(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1037,7 +1193,7 @@ func (m *AccessByIDReq) Unmarshal(dAtA []byte) error {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1065,7 +1221,7 @@ func (m *AccessByIDReq) Unmarshal(dAtA []byte) error {
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1079,11 +1235,11 @@ func (m *AccessByIDReq) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1097,7 +1253,7 @@ func (m *AccessByIDReq) Unmarshal(dAtA []byte) error {
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1111,11 +1267,11 @@ func (m *AccessByIDReq) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1124,15 +1280,15 @@ func (m *AccessByIDReq) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipInternal(dAtA[iNdEx:])
|
||||
skippy, err := skipAuthn(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1155,7 +1311,7 @@ func (m *Token) Unmarshal(dAtA []byte) error {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1183,7 +1339,7 @@ func (m *Token) Unmarshal(dAtA []byte) error {
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1197,11 +1353,11 @@ func (m *Token) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1210,15 +1366,15 @@ func (m *Token) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipInternal(dAtA[iNdEx:])
|
||||
skippy, err := skipAuthn(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1241,7 +1397,7 @@ func (m *UserID) Unmarshal(dAtA []byte) error {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1269,7 +1425,7 @@ func (m *UserID) Unmarshal(dAtA []byte) error {
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowInternal
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1283,11 +1439,11 @@ func (m *UserID) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1296,15 +1452,15 @@ func (m *UserID) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipInternal(dAtA[iNdEx:])
|
||||
skippy, err := skipAuthn(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthInternal
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
@@ -1319,7 +1475,112 @@ func (m *UserID) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipInternal(dAtA []byte) (n int, err error) {
|
||||
func (m *IssueReq) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: IssueReq: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: IssueReq: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Issuer", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Issuer = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
|
||||
}
|
||||
m.Type = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Type |= uint32(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipAuthn(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthAuthn
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipAuthn(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
depth := 0
|
||||
@@ -1327,7 +1588,7 @@ func skipInternal(dAtA []byte) (n int, err error) {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowInternal
|
||||
return 0, ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
@@ -1344,7 +1605,7 @@ func skipInternal(dAtA []byte) (n int, err error) {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowInternal
|
||||
return 0, ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
@@ -1360,7 +1621,7 @@ func skipInternal(dAtA []byte) (n int, err error) {
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowInternal
|
||||
return 0, ErrIntOverflowAuthn
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
@@ -1373,14 +1634,14 @@ func skipInternal(dAtA []byte) (n int, err error) {
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthInternal
|
||||
return 0, ErrInvalidLengthAuthn
|
||||
}
|
||||
iNdEx += length
|
||||
case 3:
|
||||
depth++
|
||||
case 4:
|
||||
if depth == 0 {
|
||||
return 0, ErrUnexpectedEndOfGroupInternal
|
||||
return 0, ErrUnexpectedEndOfGroupAuthn
|
||||
}
|
||||
depth--
|
||||
case 5:
|
||||
@@ -1389,7 +1650,7 @@ func skipInternal(dAtA []byte) (n int, err error) {
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthInternal
|
||||
return 0, ErrInvalidLengthAuthn
|
||||
}
|
||||
if depth == 0 {
|
||||
return iNdEx, nil
|
||||
@@ -1399,7 +1660,7 @@ func skipInternal(dAtA []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthInternal = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowInternal = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupInternal = fmt.Errorf("proto: unexpected end of group")
|
||||
ErrInvalidLengthAuthn = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowAuthn = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupAuthn = fmt.Errorf("proto: unexpected end of group")
|
||||
)
|
||||
@@ -13,12 +13,13 @@ service ThingsService {
|
||||
rpc Identify(Token) returns (ThingID) {}
|
||||
}
|
||||
|
||||
service UsersService {
|
||||
service AuthNService {
|
||||
rpc Issue(IssueReq) returns (Token) {}
|
||||
rpc Identify(Token) returns (UserID) {}
|
||||
}
|
||||
|
||||
message AccessByKeyReq {
|
||||
string token = 1;
|
||||
string token = 1;
|
||||
string chanID = 2;
|
||||
}
|
||||
|
||||
@@ -28,9 +29,12 @@ message ThingID {
|
||||
|
||||
message AccessByIDReq {
|
||||
string thingID = 1;
|
||||
string chanID = 2;
|
||||
string chanID = 2;
|
||||
}
|
||||
|
||||
// If a token is not carrying any information itself, the type
|
||||
// field can be used to determine how to validate the token.
|
||||
// Also, different tokens can be encoded in different ways.
|
||||
message Token {
|
||||
string value = 1;
|
||||
}
|
||||
@@ -38,3 +42,8 @@ message Token {
|
||||
message UserID {
|
||||
string value = 1;
|
||||
}
|
||||
|
||||
message IssueReq {
|
||||
string issuer = 1;
|
||||
uint32 type = 2;
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
# Authentication service
|
||||
|
||||
Authentication service provides an API for managing authentication keys.
|
||||
|
||||
There are *three types of authentication keys*:
|
||||
|
||||
- user key - keys issued to the user upon login request
|
||||
- API key - keys issued upon the user request
|
||||
- recovery key - password recovery key
|
||||
|
||||
User keys are issued when user logs in. Each user request (other than `registration` and `login`) contains user key that is used to authenticate the user. API keys are similar to the User keys. The main difference is that API keys have configurable expiration time. If no time is set, the key will never expire. For that reason, API keys are _the only key type that can be revoked_. Recovery key is the password recovery key. It's short-lived token used for password recovery process.
|
||||
|
||||
For in-depth explanation of the aforementioned scenarios, as well as thorough
|
||||
understanding of Mainflux, please check out the [official documentation][doc].
|
||||
|
||||
The following actions are supported:
|
||||
|
||||
- create (all key types)
|
||||
- verify (all key types)
|
||||
- obtain (API keys only; secret is never obtained)
|
||||
- revoke (API keys only)
|
||||
|
||||
## Configuration
|
||||
|
||||
The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|---------------------------|--------------------------------------------------------------------------|---------------|
|
||||
| MF_AUTHN_LOG_LEVEL | Service level (debug, info, warn, error) | error |
|
||||
| MF_AUTHN_DB_HOST | Database host address | localhost |
|
||||
| MF_AUTHN_DB_PORT | Database host port | 5432 |
|
||||
| MF_AUTHN_DB_USER | Database user | mainflux |
|
||||
| MF_AUTHN_DB_PASSWORD | Database password | mainflux |
|
||||
| MF_AUTHN_DB | Name of the database used by the service | auth |
|
||||
| MF_AUTHN_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
|
||||
| MF_AUTHN_DB_SSL_CERT | Path to the PEM encoded certificate file | |
|
||||
| MF_AUTHN_DB_SSL_KEY | Path to the PEM encoded key file | |
|
||||
| MF_AUTHN_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | |
|
||||
| MF_AUTHN_HTTP_PORT | Authn service HTTP port | 8180 |
|
||||
| MF_AUTHN_GRPC_PORT | Authn service gRPC port | 8181 |
|
||||
| MF_AUTHN_SERVER_CERT | Path to server certificate in pem format | |
|
||||
| MF_AUTHN_SERVER_KEY | Path to server key in pem format | |
|
||||
| MF_AUTHN_SECRET | String used for signing tokens | auth |
|
||||
| MF_JAEGER_URL | Jaeger server URL | localhost:6831 |
|
||||
|
||||
## Deployment
|
||||
|
||||
The service itself is distributed as Docker container. The following snippet
|
||||
provides a compose file template that can be used to deploy the service container
|
||||
locally:
|
||||
|
||||
```yaml
|
||||
version: "2"
|
||||
services:
|
||||
authn:
|
||||
image: mainflux/authn:[version]
|
||||
container_name: [instance name]
|
||||
ports:
|
||||
- [host machine port]:[configured HTTP port]
|
||||
environment:
|
||||
MF_AUTHN_LOG_LEVEL: [Service log level]
|
||||
MF_AUTHN_DB_HOST: [Database host address]
|
||||
MF_AUTHN_DB_PORT: [Database host port]
|
||||
MF_AUTHN_DB_USER: [Database user]
|
||||
MF_AUTHN_DB_PASS: [Database password]
|
||||
MF_AUTHN_DB: [Name of the database used by the service]
|
||||
MF_AUTHN_DB_SSL_MODE: [SSL mode to connect to the database with]
|
||||
MF_AUTHN_DB_SSL_CERT: [Path to the PEM encoded certificate file]
|
||||
MF_AUTHN_DB_SSL_KEY: [Path to the PEM encoded key file]
|
||||
MF_AUTHN_DB_SSL_ROOT_CERT: [Path to the PEM encoded root certificate file]
|
||||
MF_AUTHN_HTTP_PORT: [Service HTTP port]
|
||||
MF_AUTHN_GRPC_PORT: [Service gRPC port]
|
||||
MF_AUTHN_SECRET: [String used for signing tokens]
|
||||
MF_AUTHN_SERVER_CERT: [String path to server certificate in pem format]
|
||||
MF_AUTHN_SERVER_KEY: [String path to server key in pem format]
|
||||
MF_JAEGER_URL: [Jaeger server URL]
|
||||
```
|
||||
|
||||
To start the service outside of the container, execute the following shell script:
|
||||
|
||||
```bash
|
||||
# download the latest version of the service
|
||||
go get github.com/mainflux/mainflux
|
||||
|
||||
cd $GOPATH/src/github.com/mainflux/mainflux
|
||||
|
||||
# compile the service
|
||||
make authn
|
||||
|
||||
# copy binary to bin
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
MF_AUTHN_LOG_LEVEL=[Service log level] MF_AUTHN_DB_HOST=[Database host address] MF_AUTHN_DB_PORT=[Database host port] MF_AUTHN_DB_USER=[Database user] MF_AUTHN_DB_PASS=[Database password] MF_AUTHN_DB=[Name of the database used by the service] MF_AUTHN_DB_SSL_MODE=[SSL mode to connect to the database with] MF_AUTHN_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_AUTHN_DB_SSL_KEY=[Path to the PEM encoded key file] MF_AUTHN_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_AUTHN_HTTP_PORT=[Service HTTP port] MF_AUTHN_GRPC_PORT=[Service gRPC port] MF_AUTHN_SECRET=[String used for signing tokens] MF_AUTHN_SERVER_CERT=[Path to server certificate] MF_AUTHN_SERVER_KEY=[Path to server key] MF_JAEGER_URL=[Jaeger server URL] $GOBIN/mainflux-authn
|
||||
```
|
||||
|
||||
If `MF_EMAIL_TEMPLATE` doesn't point to any file service will function but password reset functionality will not work.
|
||||
|
||||
## Usage
|
||||
|
||||
For more information about service capabilities and its usage, please check out
|
||||
the [API documentation](swagger.yaml).
|
||||
|
||||
[doc]: http://mainflux.readthedocs.io
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package api contains implementation of AuthN service HTTP API.
|
||||
package api
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
kitot "github.com/go-kit/kit/tracing/opentracing"
|
||||
kitgrpc "github.com/go-kit/kit/transport/grpc"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var _ mainflux.AuthNServiceClient = (*grpcClient)(nil)
|
||||
|
||||
type grpcClient struct {
|
||||
issue endpoint.Endpoint
|
||||
identify endpoint.Endpoint
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewClient returns new gRPC client instance.
|
||||
func NewClient(tracer opentracing.Tracer, conn *grpc.ClientConn, timeout time.Duration) mainflux.AuthNServiceClient {
|
||||
return &grpcClient{
|
||||
issue: kitot.TraceClient(tracer, "issue")(kitgrpc.NewClient(
|
||||
conn,
|
||||
"mainflux.AuthNService",
|
||||
"Issue",
|
||||
encodeIssueRequest,
|
||||
decodeIssueResponse,
|
||||
mainflux.UserID{},
|
||||
).Endpoint()),
|
||||
identify: kitot.TraceClient(tracer, "identify")(kitgrpc.NewClient(
|
||||
conn,
|
||||
"mainflux.AuthNService",
|
||||
"Identify",
|
||||
encodeIdentifyRequest,
|
||||
decodeIdentifyResponse,
|
||||
mainflux.UserID{},
|
||||
).Endpoint()),
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (client grpcClient) Issue(ctx context.Context, req *mainflux.IssueReq, _ ...grpc.CallOption) (*mainflux.Token, error) {
|
||||
ctx, close := context.WithTimeout(ctx, client.timeout)
|
||||
defer close()
|
||||
|
||||
res, err := client.issue(ctx, issueReq{issuer: req.GetIssuer(), keyType: req.Type})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ir := res.(identityRes)
|
||||
return &mainflux.Token{Value: ir.id}, ir.err
|
||||
}
|
||||
|
||||
func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(issueReq)
|
||||
return &mainflux.IssueReq{Issuer: req.issuer, Type: req.keyType}, nil
|
||||
}
|
||||
|
||||
func decodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
res := grpcRes.(*mainflux.UserID)
|
||||
return identityRes{res.GetValue(), nil}, nil
|
||||
}
|
||||
|
||||
func (client grpcClient) Identify(ctx context.Context, token *mainflux.Token, _ ...grpc.CallOption) (*mainflux.UserID, error) {
|
||||
ctx, close := context.WithTimeout(ctx, client.timeout)
|
||||
defer close()
|
||||
|
||||
res, err := client.identify(ctx, identityReq{token: token.GetValue()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ir := res.(identityRes)
|
||||
return &mainflux.UserID{Value: ir.id}, ir.err
|
||||
}
|
||||
|
||||
func encodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(identityReq)
|
||||
return &mainflux.Token{Value: req.token}, nil
|
||||
}
|
||||
|
||||
func decodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
res := grpcRes.(*mainflux.UserID)
|
||||
return identityRes{res.GetValue(), nil}, nil
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package grpc contains implementation of users service gRPC API.
|
||||
// Package grpc contains implementation of AuthN service gRPC API.
|
||||
package grpc
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
context "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func issueEndpoint(svc authn.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(issueReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
key := authn.Key{
|
||||
Type: req.keyType,
|
||||
IssuedAt: now,
|
||||
}
|
||||
|
||||
k, err := svc.Issue(ctx, req.issuer, key)
|
||||
if err != nil {
|
||||
return identityRes{}, err
|
||||
}
|
||||
|
||||
return identityRes{k.Secret, nil}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func identifyEndpoint(svc authn.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(identityReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := svc.Identify(ctx, req.token)
|
||||
if err != nil {
|
||||
return identityRes{}, err
|
||||
}
|
||||
|
||||
return identityRes{id, nil}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
grpcapi "github.com/mainflux/mainflux/authn/api/grpc"
|
||||
"github.com/mainflux/mainflux/authn/jwt"
|
||||
"github.com/mainflux/mainflux/authn/mocks"
|
||||
"github.com/opentracing/opentracing-go/mocktracer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
port = 8081
|
||||
secret = "secret"
|
||||
email = "test@example.com"
|
||||
)
|
||||
|
||||
var svc authn.Service
|
||||
|
||||
func newService() authn.Service {
|
||||
repo := mocks.NewKeyRepository()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
t := jwt.New(secret)
|
||||
|
||||
return authn.New(repo, idp, t)
|
||||
}
|
||||
|
||||
func startGRPCServer(svc authn.Service, port int) {
|
||||
listener, _ := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
server := grpc.NewServer()
|
||||
mainflux.RegisterAuthNServiceServer(server, grpcapi.NewServer(mocktracer.New(), svc))
|
||||
go server.Serve(listener)
|
||||
}
|
||||
|
||||
func TestIssue(t *testing.T) {
|
||||
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
|
||||
authAddr := fmt.Sprintf("localhost:%d", port)
|
||||
conn, _ := grpc.Dial(authAddr, grpc.WithInsecure())
|
||||
client := grpcapi.NewClient(mocktracer.New(), conn, time.Second)
|
||||
|
||||
cases := map[string]struct {
|
||||
token string
|
||||
id string
|
||||
kind uint32
|
||||
err error
|
||||
}{
|
||||
"issue for user with valid token": {"", email, authn.UserKey, nil},
|
||||
"issue for user that doesn't exist": {"", loginKey.Secret, 32, status.Error(codes.InvalidArgument, "received invalid token request")},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
_, err := client.Issue(context.Background(), &mainflux.IssueReq{Issuer: tc.id, Type: tc.kind})
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentify(t *testing.T) {
|
||||
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
|
||||
resetKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.RecoveryKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
|
||||
|
||||
userKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute)})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
|
||||
|
||||
authAddr := fmt.Sprintf("localhost:%d", port)
|
||||
conn, _ := grpc.Dial(authAddr, grpc.WithInsecure())
|
||||
client := grpcapi.NewClient(mocktracer.New(), conn, time.Second)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
id string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "identify user with reset token",
|
||||
token: resetKey.Secret,
|
||||
id: email,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "identify user with user token",
|
||||
token: userKey.Secret,
|
||||
id: email,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "identify user with invalid login token",
|
||||
token: "invalid",
|
||||
id: "",
|
||||
err: status.Error(codes.Unauthenticated, "unauthorized access"),
|
||||
},
|
||||
{
|
||||
desc: "identify user that doesn't exist",
|
||||
token: "",
|
||||
id: "",
|
||||
err: status.Error(codes.InvalidArgument, "received invalid token request"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
id, err := client.Identify(context.Background(), &mainflux.Token{Value: tc.token})
|
||||
assert.Equal(t, tc.id, id.GetValue(), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.id, id.GetValue()))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc
|
||||
|
||||
import "github.com/mainflux/mainflux/authn"
|
||||
|
||||
type identityReq struct {
|
||||
token string
|
||||
kind uint32
|
||||
}
|
||||
|
||||
func (req identityReq) validate() error {
|
||||
if req.token == "" {
|
||||
return authn.ErrMalformedEntity
|
||||
}
|
||||
if req.kind != authn.UserKey &&
|
||||
req.kind != authn.APIKey &&
|
||||
req.kind != authn.RecoveryKey {
|
||||
return authn.ErrMalformedEntity
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type issueReq struct {
|
||||
issuer string
|
||||
keyType uint32
|
||||
}
|
||||
|
||||
func (req issueReq) validate() error {
|
||||
if req.issuer == "" {
|
||||
return authn.ErrUnauthorizedAccess
|
||||
}
|
||||
if req.keyType != authn.UserKey &&
|
||||
req.keyType != authn.APIKey &&
|
||||
req.keyType != authn.RecoveryKey {
|
||||
return authn.ErrMalformedEntity
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
kitot "github.com/go-kit/kit/tracing/opentracing"
|
||||
kitgrpc "github.com/go-kit/kit/transport/grpc"
|
||||
mainflux "github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var _ mainflux.AuthNServiceServer = (*grpcServer)(nil)
|
||||
|
||||
type grpcServer struct {
|
||||
issue kitgrpc.Handler
|
||||
identify kitgrpc.Handler
|
||||
}
|
||||
|
||||
// NewServer returns new AuthnServiceServer instance.
|
||||
func NewServer(tracer opentracing.Tracer, svc authn.Service) mainflux.AuthNServiceServer {
|
||||
return &grpcServer{
|
||||
issue: kitgrpc.NewServer(
|
||||
kitot.TraceServer(tracer, "issue")(issueEndpoint(svc)),
|
||||
decodeIssueRequest,
|
||||
encodeIssueResponse,
|
||||
),
|
||||
identify: kitgrpc.NewServer(
|
||||
kitot.TraceServer(tracer, "identify")(identifyEndpoint(svc)),
|
||||
decodeIdentifyRequest,
|
||||
encodeIdentifyResponse,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *grpcServer) Issue(ctx context.Context, req *mainflux.IssueReq) (*mainflux.Token, error) {
|
||||
_, res, err := s.issue.ServeGRPC(ctx, req)
|
||||
if err != nil {
|
||||
return nil, encodeError(err)
|
||||
}
|
||||
return res.(*mainflux.Token), nil
|
||||
}
|
||||
|
||||
func (s *grpcServer) Identify(ctx context.Context, token *mainflux.Token) (*mainflux.UserID, error) {
|
||||
_, res, err := s.identify.ServeGRPC(ctx, token)
|
||||
if err != nil {
|
||||
return nil, encodeError(err)
|
||||
}
|
||||
return res.(*mainflux.UserID), nil
|
||||
}
|
||||
|
||||
func decodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(*mainflux.IssueReq)
|
||||
return issueReq{issuer: req.GetIssuer(), keyType: req.GetType()}, nil
|
||||
}
|
||||
|
||||
func encodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
res := grpcRes.(identityRes)
|
||||
return &mainflux.Token{Value: res.id}, encodeError(res.err)
|
||||
}
|
||||
|
||||
func decodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(*mainflux.Token)
|
||||
return identityReq{token: req.GetValue()}, nil
|
||||
}
|
||||
|
||||
func encodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
res := grpcRes.(identityRes)
|
||||
return &mainflux.UserID{Value: res.id}, encodeError(res.err)
|
||||
}
|
||||
|
||||
func encodeError(err error) error {
|
||||
switch err {
|
||||
case nil:
|
||||
return nil
|
||||
case authn.ErrMalformedEntity:
|
||||
return status.Error(codes.InvalidArgument, "received invalid token request")
|
||||
case authn.ErrUnauthorizedAccess, authn.ErrKeyExpired:
|
||||
return status.Error(codes.Unauthenticated, err.Error())
|
||||
default:
|
||||
return status.Error(codes.Internal, "internal server error")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
)
|
||||
|
||||
func issueEndpoint(svc authn.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(issueKeyReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
newKey := authn.Key{
|
||||
Issuer: req.issuer,
|
||||
IssuedAt: now,
|
||||
Type: req.Type,
|
||||
}
|
||||
|
||||
duration := time.Duration(req.Duration * time.Second)
|
||||
if duration != 0 {
|
||||
exp := now.Add(duration)
|
||||
newKey.ExpiresAt = exp
|
||||
}
|
||||
|
||||
key, err := svc.Issue(ctx, req.issuer, newKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := issueKeyRes{
|
||||
ID: key.ID,
|
||||
Value: key.Secret,
|
||||
IssuedAt: key.IssuedAt,
|
||||
}
|
||||
if !key.ExpiresAt.IsZero() {
|
||||
res.ExpiresAt = &key.ExpiresAt
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
func revokeEndpoint(svc authn.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(keyReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := svc.Revoke(ctx, req.issuer, req.id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return revokeKeyRes{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveEndpoint(svc authn.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(keyReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := svc.Retrieve(ctx, req.issuer, req.id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
authn "github.com/mainflux/mainflux/authn"
|
||||
httpapi "github.com/mainflux/mainflux/authn/api/http"
|
||||
"github.com/mainflux/mainflux/authn/jwt"
|
||||
"github.com/mainflux/mainflux/authn/mocks"
|
||||
"github.com/opentracing/opentracing-go/mocktracer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
secret = "secret"
|
||||
contentType = "application/json"
|
||||
invalidEmail = "userexample.com"
|
||||
wrongID = "123e4567-e89b-12d3-a456-000000000042"
|
||||
id = "123e4567-e89b-12d3-a456-000000000001"
|
||||
email = "user@example.com"
|
||||
)
|
||||
|
||||
type issueRequest struct {
|
||||
Duration time.Duration `json:"duration,omitempty"`
|
||||
Type uint32 `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
type testRequest struct {
|
||||
client *http.Client
|
||||
method string
|
||||
url string
|
||||
contentType string
|
||||
token string
|
||||
body io.Reader
|
||||
}
|
||||
|
||||
func (tr testRequest) make() (*http.Response, error) {
|
||||
req, err := http.NewRequest(tr.method, tr.url, tr.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tr.token != "" {
|
||||
req.Header.Set("Authorization", tr.token)
|
||||
}
|
||||
if tr.contentType != "" {
|
||||
req.Header.Set("Content-Type", tr.contentType)
|
||||
}
|
||||
|
||||
req.Header.Set("Referer", "http://localhost")
|
||||
return tr.client.Do(req)
|
||||
}
|
||||
|
||||
func newService() authn.Service {
|
||||
repo := mocks.NewKeyRepository()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
t := jwt.New(secret)
|
||||
return authn.New(repo, idp, t)
|
||||
}
|
||||
|
||||
func newServer(svc authn.Service) *httptest.Server {
|
||||
mux := httpapi.MakeHandler(svc, mocktracer.New())
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
func toJSON(data interface{}) string {
|
||||
jsonData, _ := json.Marshal(data)
|
||||
return string(jsonData)
|
||||
}
|
||||
|
||||
func TestIssue(t *testing.T) {
|
||||
svc := newService()
|
||||
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
|
||||
ts := newServer(svc)
|
||||
defer ts.Close()
|
||||
client := ts.Client()
|
||||
|
||||
lk := issueRequest{Type: authn.UserKey}
|
||||
rk := issueRequest{Type: authn.RecoveryKey}
|
||||
uk := issueRequest{Type: authn.APIKey, Duration: time.Hour}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
req string
|
||||
ct string
|
||||
token string
|
||||
status int
|
||||
}{
|
||||
{
|
||||
desc: "issue login key",
|
||||
req: toJSON(lk),
|
||||
ct: contentType,
|
||||
token: "",
|
||||
status: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
desc: "issue user key",
|
||||
req: toJSON(uk),
|
||||
ct: contentType,
|
||||
token: loginKey.Secret,
|
||||
status: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
desc: "issue reset key",
|
||||
req: toJSON(rk),
|
||||
ct: contentType,
|
||||
token: loginKey.Secret,
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "issue login key wrong content type",
|
||||
req: toJSON(lk),
|
||||
ct: "", token: loginKey.Secret,
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
},
|
||||
{
|
||||
desc: "issue key wrong content type",
|
||||
req: toJSON(rk),
|
||||
ct: "",
|
||||
token: loginKey.Secret,
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
},
|
||||
{
|
||||
desc: "issue key unauthorized",
|
||||
req: toJSON(uk),
|
||||
ct: contentType,
|
||||
token: "wrong",
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
desc: "issue reset key with empty token",
|
||||
req: toJSON(rk),
|
||||
ct: contentType,
|
||||
token: "",
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "issue key with invalid request",
|
||||
req: "{",
|
||||
ct: contentType,
|
||||
token: "",
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "issue key with invalid JSON",
|
||||
req: "{invalid}",
|
||||
ct: contentType,
|
||||
token: "",
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "issue key with invalid JSON content",
|
||||
req: `{"Type":{"key":"value"}}`,
|
||||
ct: contentType,
|
||||
token: "",
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
req := testRequest{
|
||||
client: client,
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/keys", ts.URL),
|
||||
contentType: tc.ct,
|
||||
token: tc.token,
|
||||
body: strings.NewReader(tc.req),
|
||||
}
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrieve(t *testing.T) {
|
||||
svc := newService()
|
||||
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
key := authn.Key{Type: authn.APIKey, IssuedAt: time.Now()}
|
||||
|
||||
k, err := svc.Issue(context.Background(), loginKey.Secret, key)
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
|
||||
ts := newServer(svc)
|
||||
defer ts.Close()
|
||||
client := ts.Client()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
status int
|
||||
}{
|
||||
{
|
||||
desc: "retrieve an existing key",
|
||||
id: k.ID,
|
||||
token: loginKey.Secret,
|
||||
status: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "retrieve a non-existing key",
|
||||
id: "non-existing",
|
||||
token: loginKey.Secret,
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "retrieve a key unauthorized",
|
||||
id: k.ID,
|
||||
token: "wrong",
|
||||
status: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
req := testRequest{
|
||||
client: client,
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id),
|
||||
token: tc.token,
|
||||
}
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRevoke(t *testing.T) {
|
||||
svc := newService()
|
||||
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
key := authn.Key{Type: authn.APIKey, IssuedAt: time.Now()}
|
||||
|
||||
k, err := svc.Issue(context.Background(), loginKey.Secret, key)
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
|
||||
ts := newServer(svc)
|
||||
defer ts.Close()
|
||||
client := ts.Client()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
token string
|
||||
status int
|
||||
}{
|
||||
{
|
||||
desc: "revoke an existing key",
|
||||
id: k.ID,
|
||||
token: loginKey.Secret,
|
||||
status: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
desc: "revoke a non-existing key",
|
||||
id: "non-existing",
|
||||
token: loginKey.Secret,
|
||||
status: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
desc: "revoke a key unauthorized",
|
||||
id: k.ID,
|
||||
token: "wrong",
|
||||
status: http.StatusForbidden},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
req := testRequest{
|
||||
client: client,
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id),
|
||||
token: tc.token,
|
||||
}
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
)
|
||||
|
||||
type issueKeyReq struct {
|
||||
issuer string
|
||||
Type uint32 `json:"type,omitempty"`
|
||||
Duration time.Duration `json:"duration,omitempty"`
|
||||
}
|
||||
|
||||
// It is not possible to issue Reset key using HTTP API.
|
||||
func (req issueKeyReq) validate() error {
|
||||
if req.Type == authn.UserKey {
|
||||
return nil
|
||||
}
|
||||
if req.issuer == "" || (req.Type != authn.APIKey) {
|
||||
return authn.ErrMalformedEntity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type keyReq struct {
|
||||
issuer string
|
||||
id string
|
||||
}
|
||||
|
||||
func (req keyReq) validate() error {
|
||||
if req.issuer == "" || req.id == "" {
|
||||
return authn.ErrMalformedEntity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
)
|
||||
|
||||
var (
|
||||
_ mainflux.Response = (*issueKeyRes)(nil)
|
||||
_ mainflux.Response = (*revokeKeyRes)(nil)
|
||||
)
|
||||
|
||||
type issueKeyRes struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
IssuedAt time.Time `json:"issued_at,omitempty"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
}
|
||||
|
||||
func (res issueKeyRes) Code() int {
|
||||
return http.StatusCreated
|
||||
}
|
||||
|
||||
func (res issueKeyRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res issueKeyRes) Empty() bool {
|
||||
return res.Value == ""
|
||||
}
|
||||
|
||||
type revokeKeyRes struct {
|
||||
}
|
||||
|
||||
func (res revokeKeyRes) Code() int {
|
||||
return http.StatusNoContent
|
||||
}
|
||||
|
||||
func (res revokeKeyRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res revokeKeyRes) Empty() bool {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
kitot "github.com/go-kit/kit/tracing/opentracing"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/go-zoo/bone"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
const contentType = "application/json"
|
||||
|
||||
var errUnsupportedContentType = errors.New("unsupported content type")
|
||||
|
||||
// MakeHandler returns a HTTP handler for API endpoints.
|
||||
func MakeHandler(svc authn.Service, tracer opentracing.Tracer) http.Handler {
|
||||
opts := []kithttp.ServerOption{
|
||||
kithttp.ServerErrorEncoder(encodeError),
|
||||
}
|
||||
|
||||
mux := bone.New()
|
||||
|
||||
mux.Post("/keys", kithttp.NewServer(
|
||||
kitot.TraceServer(tracer, "issue")(issueEndpoint(svc)),
|
||||
decodeIssue,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
))
|
||||
|
||||
mux.Get("/keys/:id", kithttp.NewServer(
|
||||
kitot.TraceServer(tracer, "retrieve")(retrieveEndpoint(svc)),
|
||||
decodeKeyReq,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
))
|
||||
|
||||
mux.Delete("/keys/:id", kithttp.NewServer(
|
||||
kitot.TraceServer(tracer, "revoke")(revokeEndpoint(svc)),
|
||||
decodeKeyReq,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
))
|
||||
|
||||
mux.GetFunc("/version", mainflux.Version("auth"))
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func decodeIssue(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
|
||||
return nil, errUnsupportedContentType
|
||||
}
|
||||
req := issueKeyReq{
|
||||
issuer: r.Header.Get("Authorization"),
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeKeyReq(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
req := keyReq{
|
||||
issuer: r.Header.Get("Authorization"),
|
||||
id: bone.GetValue(r, "id"),
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
if ar, ok := response.(mainflux.Response); ok {
|
||||
for k, v := range ar.Headers() {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
|
||||
w.WriteHeader(ar.Code())
|
||||
|
||||
if ar.Empty() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
switch err {
|
||||
case authn.ErrMalformedEntity:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
case authn.ErrUnauthorizedAccess:
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
case authn.ErrNotFound:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
case authn.ErrConflict:
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
case io.EOF, io.ErrUnexpectedEOF:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
case errUnsupportedContentType:
|
||||
w.WriteHeader(http.StatusUnsupportedMediaType)
|
||||
default:
|
||||
switch err.(type) {
|
||||
case *json.SyntaxError:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
case *json.UnmarshalTypeError:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
default:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +build !test
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
)
|
||||
|
||||
var _ authn.Service = (*loggingMiddleware)(nil)
|
||||
|
||||
type loggingMiddleware struct {
|
||||
logger log.Logger
|
||||
svc authn.Service
|
||||
}
|
||||
|
||||
// LoggingMiddleware adds logging facilities to the core service.
|
||||
func LoggingMiddleware(svc authn.Service, logger log.Logger) authn.Service {
|
||||
return &loggingMiddleware{logger, svc}
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Issue(ctx context.Context, issuer string, newKey authn.Key) (key authn.Key, err error) {
|
||||
defer func(begin time.Time) {
|
||||
d := "infinite duration"
|
||||
if !key.ExpiresAt.IsZero() {
|
||||
d = fmt.Sprintf("the key with expiration date %v", key.ExpiresAt)
|
||||
}
|
||||
message := fmt.Sprintf("Method issue for %s took %s to complete", d, time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Issue(ctx, issuer, newKey)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Revoke(ctx context.Context, owner, id string) (err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method revoke for key %s took %s to complete", id, time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Revoke(ctx, owner, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Retrieve(ctx context.Context, owner, id string) (key authn.Key, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method retrieve for key %s took %s to complete", id, time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Retrieve(ctx, owner, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Identify(ctx context.Context, key string) (id string, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method identify took %s to complete", time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Identify(ctx, key)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
)
|
||||
|
||||
var _ authn.Service = (*metricsMiddleware)(nil)
|
||||
|
||||
type metricsMiddleware struct {
|
||||
counter metrics.Counter
|
||||
latency metrics.Histogram
|
||||
svc authn.Service
|
||||
}
|
||||
|
||||
// MetricsMiddleware instruments core service by tracking request count and
|
||||
// latency.
|
||||
func MetricsMiddleware(svc authn.Service, counter metrics.Counter, latency metrics.Histogram) authn.Service {
|
||||
return &metricsMiddleware{
|
||||
counter: counter,
|
||||
latency: latency,
|
||||
svc: svc,
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) Issue(ctx context.Context, issuer string, key authn.Key) (authn.Key, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "issue").Add(1)
|
||||
ms.latency.With("method", "issue").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.Issue(ctx, issuer, key)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) Revoke(ctx context.Context, issuer, id string) error {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "revoke").Add(1)
|
||||
ms.latency.With("method", "revoke").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.Revoke(ctx, issuer, id)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) Retrieve(ctx context.Context, issuer, id string) (authn.Key, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "retrieve").Add(1)
|
||||
ms.latency.With("method", "retrieve").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.Retrieve(ctx, issuer, id)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) Identify(ctx context.Context, key string) (string, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "identify").Add(1)
|
||||
ms.latency.With("method", "identify").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.Identify(ctx, key)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package authn
|
||||
|
||||
// IdentityProvider specifies an API for generating unique identifiers.
|
||||
type IdentityProvider interface {
|
||||
// ID generates the unique identifier.
|
||||
ID() (string, error)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jwt_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
"github.com/mainflux/mainflux/authn/jwt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const secret = "test"
|
||||
|
||||
func key() authn.Key {
|
||||
exp := time.Now().UTC().Add(10 * time.Minute).Round(time.Second)
|
||||
return authn.Key{
|
||||
ID: "id",
|
||||
Type: authn.UserKey,
|
||||
Issuer: "user@email.com",
|
||||
Secret: "",
|
||||
IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second),
|
||||
ExpiresAt: exp,
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue(t *testing.T) {
|
||||
tokenizer := jwt.New(secret)
|
||||
|
||||
emptyIssuer := key()
|
||||
emptyIssuer.Issuer = ""
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
key authn.Key
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "issue new token",
|
||||
key: key(),
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
_, err := tokenizer.Issue(tc.key)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
tokenizer := jwt.New(secret)
|
||||
|
||||
token, err := tokenizer.Issue(key())
|
||||
require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err))
|
||||
|
||||
userKey := key()
|
||||
userKey.Type = authn.APIKey
|
||||
userKey.ExpiresAt = time.Now().UTC().Add(-1 * time.Minute).Round(time.Second)
|
||||
userToken, err := tokenizer.Issue(userKey)
|
||||
require.Nil(t, err, fmt.Sprintf("issuing user key expected to succeed: %s", err))
|
||||
|
||||
expKey := key()
|
||||
expKey.ExpiresAt = time.Now().UTC().Add(-1 * time.Minute).Round(time.Second)
|
||||
expToken, err := tokenizer.Issue(expKey)
|
||||
require.Nil(t, err, fmt.Sprintf("issuing expired key expected to succeed: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
key authn.Key
|
||||
token string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "parse valid key",
|
||||
key: key(),
|
||||
token: token,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "parse ivalid key",
|
||||
key: authn.Key{},
|
||||
token: "invalid",
|
||||
err: authn.ErrUnauthorizedAccess,
|
||||
},
|
||||
|
||||
{
|
||||
desc: "parse expired key",
|
||||
key: authn.Key{},
|
||||
token: expToken,
|
||||
err: authn.ErrKeyExpired,
|
||||
},
|
||||
{
|
||||
desc: "parse expired user key",
|
||||
key: userKey,
|
||||
token: userToken,
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
key, err := tokenizer.Parse(tc.token)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.key, key, fmt.Sprintf("%s expected %v, got %v", tc.desc, tc.key, key))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
)
|
||||
|
||||
type claims struct {
|
||||
jwt.StandardClaims
|
||||
Type *uint32 `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
func (c claims) Valid() error {
|
||||
if c.Type == nil || *c.Type > authn.APIKey {
|
||||
return authn.ErrMalformedEntity
|
||||
}
|
||||
|
||||
return c.StandardClaims.Valid()
|
||||
}
|
||||
|
||||
type tokenizer struct {
|
||||
secret string
|
||||
}
|
||||
|
||||
// New returns new JWT Tokenizer.
|
||||
func New(secret string) authn.Tokenizer {
|
||||
return tokenizer{secret: secret}
|
||||
}
|
||||
|
||||
func (svc tokenizer) Issue(key authn.Key) (string, error) {
|
||||
claims := claims{
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
Issuer: key.Issuer,
|
||||
Subject: key.Secret,
|
||||
IssuedAt: key.IssuedAt.UTC().Unix(),
|
||||
},
|
||||
Type: &key.Type,
|
||||
}
|
||||
|
||||
if !key.ExpiresAt.IsZero() {
|
||||
claims.ExpiresAt = key.ExpiresAt.UTC().Unix()
|
||||
}
|
||||
if key.ID != "" {
|
||||
claims.Id = key.ID
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(svc.secret))
|
||||
}
|
||||
|
||||
func (svc tokenizer) Parse(token string) (authn.Key, error) {
|
||||
c := claims{}
|
||||
_, err := jwt.ParseWithClaims(token, &c, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, authn.ErrUnauthorizedAccess
|
||||
}
|
||||
return []byte(svc.secret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if e, ok := err.(*jwt.ValidationError); ok && e.Errors == jwt.ValidationErrorExpired {
|
||||
// Expired User key needs to be revoked.
|
||||
if c.Type != nil && *c.Type == authn.APIKey {
|
||||
return c.toKey(), nil
|
||||
}
|
||||
return authn.Key{}, authn.ErrKeyExpired
|
||||
}
|
||||
return authn.Key{}, authn.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
return c.toKey(), nil
|
||||
}
|
||||
|
||||
func (c claims) toKey() authn.Key {
|
||||
key := authn.Key{
|
||||
ID: c.Id,
|
||||
Issuer: c.Issuer,
|
||||
Secret: c.Subject,
|
||||
IssuedAt: time.Unix(c.IssuedAt, 0).UTC(),
|
||||
}
|
||||
if c.ExpiresAt != 0 {
|
||||
key.ExpiresAt = time.Unix(c.ExpiresAt, 0).UTC()
|
||||
}
|
||||
|
||||
// Default type is 0.
|
||||
if c.Type != nil {
|
||||
key.Type = *c.Type
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package authn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidKeyIssuedAt indicates that the Key is being used before it's issued.
|
||||
ErrInvalidKeyIssuedAt = errors.New("invalid issue time")
|
||||
|
||||
// ErrKeyExpired indicates that the Key is expired.
|
||||
ErrKeyExpired = errors.New("use of expired key")
|
||||
)
|
||||
|
||||
const (
|
||||
// UserKey is temporary User key received on successfull login.
|
||||
UserKey uint32 = iota
|
||||
// RecoveryKey represents a key for resseting password.
|
||||
RecoveryKey
|
||||
// APIKey enables the one to act on behalf of the user.
|
||||
APIKey
|
||||
)
|
||||
|
||||
// Key represents API key.
|
||||
type Key struct {
|
||||
ID string
|
||||
Type uint32
|
||||
Issuer string
|
||||
Secret string
|
||||
IssuedAt time.Time
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
// Expired verifies if the key is expired.
|
||||
func (k Key) Expired() bool {
|
||||
return k.ExpiresAt.UTC().Before(time.Now().UTC())
|
||||
}
|
||||
|
||||
// KeyRepository specifies Key persistence API.
|
||||
type KeyRepository interface {
|
||||
// Save persists the Key. A non-nil error is returned to indicate
|
||||
// operation failure
|
||||
Save(context.Context, Key) (string, error)
|
||||
|
||||
// Retrieve retrieves Key by its unique identifier.
|
||||
Retrieve(context.Context, string, string) (Key, error)
|
||||
|
||||
// Remove removes Key with provided ID.
|
||||
Remove(context.Context, string, string) error
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package authn_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExpired(t *testing.T) {
|
||||
exp := time.Now().Add(5 * time.Minute)
|
||||
exp1 := time.Now()
|
||||
cases := []struct {
|
||||
desc string
|
||||
key authn.Key
|
||||
expired bool
|
||||
}{
|
||||
{
|
||||
desc: "not expired key",
|
||||
key: authn.Key{
|
||||
IssuedAt: time.Now(),
|
||||
ExpiresAt: exp,
|
||||
},
|
||||
expired: false,
|
||||
},
|
||||
{
|
||||
desc: "expired key",
|
||||
key: authn.Key{
|
||||
IssuedAt: time.Now().UTC().Add(2 * time.Minute),
|
||||
ExpiresAt: exp1,
|
||||
},
|
||||
expired: true,
|
||||
},
|
||||
{
|
||||
desc: "key with no expiration date",
|
||||
key: authn.Key{
|
||||
IssuedAt: time.Now(),
|
||||
},
|
||||
expired: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := tc.key.Expired()
|
||||
assert.Equal(t, tc.expired, res, fmt.Sprintf("%s: expected %t got %t\n", tc.desc, tc.expired, res))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
)
|
||||
|
||||
var _ authn.IdentityProvider = (*identityProviderMock)(nil)
|
||||
|
||||
type identityProviderMock struct {
|
||||
mu sync.Mutex
|
||||
counter int
|
||||
}
|
||||
|
||||
func (idp *identityProviderMock) ID() (string, error) {
|
||||
idp.mu.Lock()
|
||||
defer idp.mu.Unlock()
|
||||
|
||||
idp.counter++
|
||||
return fmt.Sprintf("%s%012d", "123e4567-e89b-12d3-a456-", idp.counter), nil
|
||||
}
|
||||
|
||||
// NewIdentityProvider creates "mirror" identity provider, i.e. generated
|
||||
// token will hold value provided by the caller.
|
||||
func NewIdentityProvider() authn.IdentityProvider {
|
||||
return &identityProviderMock{}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
)
|
||||
|
||||
var _ authn.KeyRepository = (*keyRepositoryMock)(nil)
|
||||
|
||||
type keyRepositoryMock struct {
|
||||
mu sync.Mutex
|
||||
keys map[string]authn.Key
|
||||
}
|
||||
|
||||
// NewKeyRepository creates in-memory user repository
|
||||
func NewKeyRepository() authn.KeyRepository {
|
||||
return &keyRepositoryMock{
|
||||
keys: make(map[string]authn.Key),
|
||||
}
|
||||
}
|
||||
|
||||
func (krm *keyRepositoryMock) Save(ctx context.Context, key authn.Key) (string, error) {
|
||||
krm.mu.Lock()
|
||||
defer krm.mu.Unlock()
|
||||
|
||||
if _, ok := krm.keys[key.ID]; ok {
|
||||
return "", authn.ErrConflict
|
||||
}
|
||||
|
||||
krm.keys[key.ID] = key
|
||||
return key.ID, nil
|
||||
}
|
||||
func (krm *keyRepositoryMock) Retrieve(ctx context.Context, issuer, id string) (authn.Key, error) {
|
||||
krm.mu.Lock()
|
||||
defer krm.mu.Unlock()
|
||||
|
||||
if key, ok := krm.keys[id]; ok && key.Issuer == issuer {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
return authn.Key{}, authn.ErrNotFound
|
||||
}
|
||||
func (krm *keyRepositoryMock) Remove(ctx context.Context, issuer, id string) error {
|
||||
krm.mu.Lock()
|
||||
defer krm.mu.Unlock()
|
||||
if key, ok := krm.keys[id]; ok && key.Issuer == issuer {
|
||||
delete(krm.keys, id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package postgres contains Key repository implementations using
|
||||
// PostgreSQL as the underlying database.
|
||||
package postgres
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
)
|
||||
|
||||
// Config defines the options that are used when connecting to a PostgreSQL instance
|
||||
type Config struct {
|
||||
Host string
|
||||
Port string
|
||||
User string
|
||||
Pass string
|
||||
Name string
|
||||
SSLMode string
|
||||
SSLCert string
|
||||
SSLKey string
|
||||
SSLRootCert string
|
||||
}
|
||||
|
||||
// Connect creates a connection to the PostgreSQL instance and applies any
|
||||
// unapplied database migrations. A non-nil error is returned to indicate
|
||||
// failure.
|
||||
func Connect(cfg Config) (*sqlx.DB, error) {
|
||||
url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert)
|
||||
|
||||
db, err := sqlx.Open("postgres", url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := migrateDB(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func migrateDB(db *sqlx.DB) error {
|
||||
migrations := &migrate.MemoryMigrationSource{
|
||||
Migrations: []*migrate.Migration{
|
||||
{
|
||||
Id: "authn",
|
||||
Up: []string{
|
||||
`CREATE TABLE IF NOT EXISTS keys (
|
||||
id UUID NOT NULL,
|
||||
type SMALLINT,
|
||||
issuer VARCHAR(254) NOT NULL,
|
||||
issued_at TIMESTAMP NOT NULL,
|
||||
expires_at TIMESTAMP,
|
||||
PRIMARY KEY (id, issuer)
|
||||
)`,
|
||||
},
|
||||
Down: []string{"DROP TABLE IF EXISTS keys"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
)
|
||||
|
||||
var _ authn.KeyRepository = (*repo)(nil)
|
||||
|
||||
const (
|
||||
errDuplicate = "unique_violation"
|
||||
errInvalid = "invalid_text_representation"
|
||||
)
|
||||
|
||||
type repo struct {
|
||||
db Database
|
||||
}
|
||||
|
||||
// New instantiates a PostgreSQL implementation of key repository.
|
||||
func New(db Database) authn.KeyRepository {
|
||||
return &repo{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (kr repo) Save(ctx context.Context, key authn.Key) (string, error) {
|
||||
q := `INSERT INTO keys (id, type, issuer, issued_at, expires_at)
|
||||
VALUES (:id, :type, :issuer, :issued_at, :expires_at)`
|
||||
|
||||
dbKey := toDBKey(key)
|
||||
if _, err := kr.db.NamedExecContext(ctx, q, dbKey); err != nil {
|
||||
|
||||
pqErr, ok := err.(*pq.Error)
|
||||
if ok {
|
||||
if pqErr.Code.Name() == errDuplicate {
|
||||
return "", authn.ErrConflict
|
||||
}
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dbKey.ID, nil
|
||||
}
|
||||
|
||||
func (kr repo) Retrieve(ctx context.Context, issuer, id string) (authn.Key, error) {
|
||||
q := `SELECT id, type, issuer, issued_at, expires_at FROM keys WHERE issuer = $1 AND id = $2`
|
||||
key := dbKey{}
|
||||
if err := kr.db.QueryRowxContext(ctx, q, issuer, id).StructScan(&key); err != nil {
|
||||
pqErr, ok := err.(*pq.Error)
|
||||
if err == sql.ErrNoRows || ok && errInvalid == pqErr.Code.Name() {
|
||||
return authn.Key{}, authn.ErrNotFound
|
||||
}
|
||||
|
||||
return authn.Key{}, err
|
||||
}
|
||||
|
||||
return toKey(key), nil
|
||||
}
|
||||
|
||||
func (kr repo) Remove(ctx context.Context, issuer, id string) error {
|
||||
q := `DELETE FROM keys WHERE issuer = :issuer AND id = :id`
|
||||
key := dbKey{
|
||||
ID: id,
|
||||
Issuer: issuer,
|
||||
}
|
||||
if _, err := kr.db.NamedExecContext(ctx, q, key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type dbKey struct {
|
||||
ID string `db:"id"`
|
||||
Type uint32 `db:"type"`
|
||||
Issuer string `db:"issuer"`
|
||||
Revoked bool `db:"revoked"`
|
||||
IssuedAt time.Time `db:"issued_at"`
|
||||
ExpiresAt sql.NullTime `db:"expires_at"`
|
||||
}
|
||||
|
||||
func toDBKey(key authn.Key) dbKey {
|
||||
ret := dbKey{
|
||||
ID: key.ID,
|
||||
Type: key.Type,
|
||||
Issuer: key.Issuer,
|
||||
IssuedAt: key.IssuedAt,
|
||||
}
|
||||
if !key.ExpiresAt.IsZero() {
|
||||
ret.ExpiresAt = sql.NullTime{Time: key.ExpiresAt, Valid: true}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func toKey(key dbKey) authn.Key {
|
||||
ret := authn.Key{
|
||||
ID: key.ID,
|
||||
Type: key.Type,
|
||||
Issuer: key.Issuer,
|
||||
IssuedAt: key.IssuedAt,
|
||||
}
|
||||
if key.ExpiresAt.Valid {
|
||||
ret.ExpiresAt = key.ExpiresAt.Time
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
"github.com/mainflux/mainflux/authn/postgres"
|
||||
"github.com/mainflux/mainflux/authn/uuid"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKeySave(t *testing.T) {
|
||||
dbMiddleware := postgres.NewDatabase(db)
|
||||
repo := postgres.New(dbMiddleware)
|
||||
|
||||
email := "user-save@example.com"
|
||||
expTime := time.Now().Add(5 * time.Minute)
|
||||
idp := uuid.New()
|
||||
id, _ := idp.ID()
|
||||
cases := []struct {
|
||||
desc string
|
||||
key authn.Key
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "save a new key",
|
||||
key: authn.Key{
|
||||
Issuer: email,
|
||||
IssuedAt: time.Now(),
|
||||
ExpiresAt: expTime,
|
||||
ID: id,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "save with duplicate id",
|
||||
key: authn.Key{
|
||||
Issuer: email,
|
||||
IssuedAt: time.Now(),
|
||||
ExpiresAt: expTime,
|
||||
ID: id,
|
||||
},
|
||||
err: authn.ErrConflict,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
_, err := repo.Save(context.Background(), tc.key)
|
||||
assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyRetrieve(t *testing.T) {
|
||||
dbMiddleware := postgres.NewDatabase(db)
|
||||
repo := postgres.New(dbMiddleware)
|
||||
|
||||
email := "user-save@example.com"
|
||||
expTime := time.Now().Add(5 * time.Minute)
|
||||
idp := uuid.New()
|
||||
id, _ := idp.ID()
|
||||
key := authn.Key{
|
||||
Issuer: email,
|
||||
IssuedAt: time.Now(),
|
||||
ExpiresAt: expTime,
|
||||
ID: id,
|
||||
}
|
||||
_, err := repo.Save(context.Background(), key)
|
||||
assert.Nil(t, err, fmt.Sprintf("Storing Key expected to succeed: %s", err))
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
issuer string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "retrieve an existing key",
|
||||
id: key.ID,
|
||||
issuer: key.Issuer,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "retrieve unauthorized",
|
||||
id: key.ID,
|
||||
issuer: "",
|
||||
err: authn.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "retrieve unknown key",
|
||||
id: "",
|
||||
issuer: key.Issuer,
|
||||
err: authn.ErrNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
_, err := repo.Retrieve(context.Background(), tc.issuer, tc.id)
|
||||
assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyRemove(t *testing.T) {
|
||||
dbMiddleware := postgres.NewDatabase(db)
|
||||
repo := postgres.New(dbMiddleware)
|
||||
|
||||
email := "user-save@example.com"
|
||||
expTime := time.Now().Add(5 * time.Minute)
|
||||
idp := uuid.New()
|
||||
id, _ := idp.ID()
|
||||
key := authn.Key{
|
||||
Issuer: email,
|
||||
IssuedAt: time.Now(),
|
||||
ExpiresAt: expTime,
|
||||
ID: id,
|
||||
}
|
||||
_, err := repo.Save(opentracing.ContextWithSpan(context.Background(), opentracing.StartSpan("")), key)
|
||||
assert.Nil(t, err, fmt.Sprintf("Storing Key expected to succeed: %s", err))
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
issuer string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "remove an existing key",
|
||||
id: key.ID,
|
||||
issuer: key.Issuer,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "remove key that does not exist",
|
||||
id: key.ID,
|
||||
issuer: key.Issuer,
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
err := repo.Remove(context.Background(), tc.issuer, tc.id)
|
||||
assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package postgres_test contains tests for PostgreSQL repository
|
||||
// implementations.
|
||||
package postgres_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/mainflux/mainflux/authn/postgres"
|
||||
dockertest "gopkg.in/ory-am/dockertest.v3"
|
||||
)
|
||||
|
||||
const wrong string = "wrong-value"
|
||||
|
||||
var db *sqlx.DB
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
log.Fatalf("Could not connect to docker: %s", err)
|
||||
}
|
||||
|
||||
cfg := []string{
|
||||
"POSTGRES_USER=test",
|
||||
"POSTGRES_PASSWORD=test",
|
||||
"POSTGRES_DB=test",
|
||||
}
|
||||
container, err := pool.Run("postgres", "10.2-alpine", cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start container: %s", err)
|
||||
}
|
||||
|
||||
port := container.GetPort("5432/tcp")
|
||||
|
||||
if err := pool.Retry(func() error {
|
||||
url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
|
||||
db, err := sql.Open("postgres", url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Ping()
|
||||
}); err != nil {
|
||||
log.Fatalf("Could not connect to docker: %s", err)
|
||||
}
|
||||
|
||||
dbConfig := postgres.Config{
|
||||
Host: "localhost",
|
||||
Port: port,
|
||||
User: "test",
|
||||
Pass: "test",
|
||||
Name: "test",
|
||||
SSLMode: "disable",
|
||||
SSLCert: "",
|
||||
SSLKey: "",
|
||||
SSLRootCert: "",
|
||||
}
|
||||
|
||||
if db, err = postgres.Connect(dbConfig); err != nil {
|
||||
log.Fatalf("Could not setup test DB connection: %s", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
code := m.Run()
|
||||
|
||||
if err := pool.Purge(container); err != nil {
|
||||
log.Fatalf("Could not purge container: %s", err)
|
||||
}
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
var _ Database = (*database)(nil)
|
||||
|
||||
type database struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// Database provides a database interface
|
||||
type Database interface {
|
||||
NamedExecContext(context.Context, string, interface{}) (sql.Result, error)
|
||||
QueryRowxContext(context.Context, string, ...interface{}) *sqlx.Row
|
||||
}
|
||||
|
||||
// NewDatabase creates a ThingDatabase instance
|
||||
func NewDatabase(db *sqlx.DB) Database {
|
||||
return &database{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (d database) NamedExecContext(ctx context.Context, query string, args interface{}) (sql.Result, error) {
|
||||
addSpanTags(ctx, query)
|
||||
return d.db.NamedExecContext(ctx, query, args)
|
||||
}
|
||||
|
||||
func (d database) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row {
|
||||
addSpanTags(ctx, query)
|
||||
return d.db.QueryRowxContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
func addSpanTags(ctx context.Context, query string) {
|
||||
span := opentracing.SpanFromContext(ctx)
|
||||
if span != nil {
|
||||
span.SetTag("sql.statement", query)
|
||||
span.SetTag("span.kind", "client")
|
||||
span.SetTag("peer.service", "postgres")
|
||||
span.SetTag("db.type", "sql")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package authn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
loginDuration = 10 * time.Hour
|
||||
resetDuration = 5 * time.Minute
|
||||
issuerName = "mainflux.authn"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnauthorizedAccess represents unauthorized access.
|
||||
ErrUnauthorizedAccess = errors.New("unauthorized access")
|
||||
|
||||
// ErrMalformedEntity indicates malformed entity specification (e.g.
|
||||
// invalid owner or ID).
|
||||
ErrMalformedEntity = errors.New("malformed entity specification")
|
||||
|
||||
// ErrNotFound indicates a non-existing entity request.
|
||||
ErrNotFound = errors.New("entity not found")
|
||||
|
||||
// ErrConflict indicates that entity already exists.
|
||||
ErrConflict = errors.New("entity already exists")
|
||||
)
|
||||
|
||||
// Service specifies an API that must be fullfiled by the domain service
|
||||
// implementation, and all of its decorators (e.g. logging & metrics).
|
||||
type Service interface {
|
||||
// Issue issues a new Key.
|
||||
Issue(context.Context, string, Key) (Key, error)
|
||||
|
||||
// Revoke removes the Key with the provided id that is
|
||||
// issued by the user identified by the provided key.
|
||||
Revoke(context.Context, string, string) error
|
||||
|
||||
// Retrieve retrieves data for the Key identified by the provided
|
||||
// ID, that is issued by the user identified by the provided key.
|
||||
Retrieve(context.Context, string, string) (Key, error)
|
||||
|
||||
// Identify validates token token. If token is valid, content
|
||||
// is returned. If token is invalid, or invocation failed for some
|
||||
// other reason, non-nil error value is returned in response.
|
||||
Identify(context.Context, string) (string, error)
|
||||
}
|
||||
|
||||
var _ Service = (*service)(nil)
|
||||
|
||||
type service struct {
|
||||
keys KeyRepository
|
||||
idp IdentityProvider
|
||||
tokenizer Tokenizer
|
||||
}
|
||||
|
||||
// New instantiates the auth service implementation.
|
||||
func New(keys KeyRepository, idp IdentityProvider, tokenizer Tokenizer) Service {
|
||||
return &service{
|
||||
tokenizer: tokenizer,
|
||||
keys: keys,
|
||||
idp: idp,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc service) Issue(ctx context.Context, issuer string, key Key) (Key, error) {
|
||||
if key.IssuedAt.IsZero() {
|
||||
return Key{}, ErrInvalidKeyIssuedAt
|
||||
}
|
||||
switch key.Type {
|
||||
case APIKey:
|
||||
return svc.userKey(ctx, issuer, key)
|
||||
case RecoveryKey:
|
||||
return svc.resetKey(ctx, issuer, key)
|
||||
default:
|
||||
return svc.loginKey(issuer, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (svc service) Revoke(ctx context.Context, issuer, id string) error {
|
||||
email, err := svc.login(issuer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return svc.keys.Remove(ctx, email, id)
|
||||
}
|
||||
|
||||
func (svc service) Retrieve(ctx context.Context, issuer, id string) (Key, error) {
|
||||
email, err := svc.login(issuer)
|
||||
if err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
|
||||
return svc.keys.Retrieve(ctx, email, id)
|
||||
}
|
||||
|
||||
func (svc service) Identify(ctx context.Context, token string) (string, error) {
|
||||
c, err := svc.tokenizer.Parse(token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch c.Type {
|
||||
case APIKey:
|
||||
k, err := svc.keys.Retrieve(ctx, c.Issuer, c.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Auto revoke expired key.
|
||||
if k.Expired() {
|
||||
svc.keys.Remove(ctx, c.Issuer, c.ID)
|
||||
return "", ErrKeyExpired
|
||||
}
|
||||
return c.Issuer, nil
|
||||
case RecoveryKey, UserKey:
|
||||
if c.Issuer != issuerName {
|
||||
return "", ErrUnauthorizedAccess
|
||||
}
|
||||
return c.Secret, nil
|
||||
default:
|
||||
return "", ErrUnauthorizedAccess
|
||||
}
|
||||
}
|
||||
|
||||
func (svc service) loginKey(issuer string, key Key) (Key, error) {
|
||||
key.Secret = issuer
|
||||
return svc.tempKey(loginDuration, key)
|
||||
}
|
||||
|
||||
func (svc service) resetKey(ctx context.Context, issuer string, key Key) (Key, error) {
|
||||
issuer, err := svc.login(issuer)
|
||||
if err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
key.Secret = issuer
|
||||
|
||||
return svc.tempKey(resetDuration, key)
|
||||
}
|
||||
|
||||
func (svc service) tempKey(duration time.Duration, key Key) (Key, error) {
|
||||
key.Issuer = issuerName
|
||||
key.ExpiresAt = key.IssuedAt.Add(duration)
|
||||
val, err := svc.tokenizer.Issue(key)
|
||||
if err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
|
||||
key.Secret = val
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (svc service) userKey(ctx context.Context, issuer string, key Key) (Key, error) {
|
||||
email, err := svc.login(issuer)
|
||||
if err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
key.Issuer = email
|
||||
|
||||
id, err := svc.idp.ID()
|
||||
if err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
key.ID = id
|
||||
|
||||
value, err := svc.tokenizer.Issue(key)
|
||||
if err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
key.Secret = value
|
||||
|
||||
if _, err := svc.keys.Save(ctx, key); err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (svc service) login(token string) (string, error) {
|
||||
c, err := svc.tokenizer.Parse(token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Only user key token is valid for login.
|
||||
if c.Type != UserKey {
|
||||
return "", ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
if c.Secret == "" {
|
||||
return "", ErrUnauthorizedAccess
|
||||
}
|
||||
return c.Secret, nil
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package authn_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
"github.com/mainflux/mainflux/authn/jwt"
|
||||
"github.com/mainflux/mainflux/authn/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
secret = "secret"
|
||||
email = "test@example.com"
|
||||
)
|
||||
|
||||
func newService() authn.Service {
|
||||
repo := mocks.NewKeyRepository()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
t := jwt.New(secret)
|
||||
return authn.New(repo, idp, t)
|
||||
}
|
||||
|
||||
func TestIssue(t *testing.T) {
|
||||
svc := newService()
|
||||
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
key authn.Key
|
||||
issuer string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "issue login key",
|
||||
key: authn.Key{
|
||||
Type: authn.UserKey,
|
||||
IssuedAt: time.Now(),
|
||||
},
|
||||
issuer: email,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "issue login key no issue time",
|
||||
key: authn.Key{
|
||||
Type: authn.UserKey,
|
||||
},
|
||||
issuer: email,
|
||||
err: authn.ErrInvalidKeyIssuedAt,
|
||||
},
|
||||
{
|
||||
desc: "issue user key",
|
||||
key: authn.Key{
|
||||
Type: authn.APIKey,
|
||||
IssuedAt: time.Now(),
|
||||
},
|
||||
issuer: loginKey.Secret,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "issue user key unauthorized",
|
||||
key: authn.Key{
|
||||
Type: authn.APIKey,
|
||||
IssuedAt: time.Now(),
|
||||
},
|
||||
issuer: "",
|
||||
err: authn.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "issue user key no issue time",
|
||||
key: authn.Key{
|
||||
Type: authn.APIKey,
|
||||
},
|
||||
issuer: loginKey.Secret,
|
||||
err: authn.ErrInvalidKeyIssuedAt,
|
||||
},
|
||||
{
|
||||
desc: "issue reset key",
|
||||
key: authn.Key{
|
||||
Type: authn.RecoveryKey,
|
||||
IssuedAt: time.Now(),
|
||||
},
|
||||
issuer: loginKey.Secret,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "issue reset key no issue time",
|
||||
key: authn.Key{
|
||||
Type: authn.RecoveryKey,
|
||||
},
|
||||
issuer: loginKey.Secret,
|
||||
err: authn.ErrInvalidKeyIssuedAt,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
_, err := svc.Issue(context.Background(), tc.issuer, tc.key)
|
||||
assert.Equal(t, err, tc.err, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
func TestRevoke(t *testing.T) {
|
||||
svc := newService()
|
||||
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
key := authn.Key{
|
||||
Type: authn.APIKey,
|
||||
IssuedAt: time.Now(),
|
||||
}
|
||||
newKey, err := svc.Issue(context.Background(), loginKey.Secret, key)
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
issuer string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "revoke user key",
|
||||
id: newKey.ID,
|
||||
issuer: loginKey.Secret,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "revoke non-existing user key",
|
||||
id: newKey.ID,
|
||||
issuer: loginKey.Secret,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "revoke unauthorized",
|
||||
id: newKey.ID,
|
||||
issuer: "",
|
||||
err: authn.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
err := svc.Revoke(context.Background(), tc.issuer, tc.id)
|
||||
assert.Equal(t, err, tc.err, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
func TestRetrieve(t *testing.T) {
|
||||
svc := newService()
|
||||
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
key := authn.Key{
|
||||
ID: "id",
|
||||
Type: authn.APIKey,
|
||||
IssuedAt: time.Now(),
|
||||
}
|
||||
newKey, err := svc.Issue(context.Background(), loginKey.Secret, key)
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
|
||||
|
||||
resetKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.RecoveryKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
|
||||
|
||||
userKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
issuer string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "retrieve user key",
|
||||
id: newKey.ID,
|
||||
issuer: loginKey.Secret,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "retrieve non-existing user key",
|
||||
id: "invalid",
|
||||
issuer: loginKey.Secret,
|
||||
err: authn.ErrNotFound,
|
||||
},
|
||||
{
|
||||
desc: "retrieve unauthorized",
|
||||
id: newKey.ID,
|
||||
issuer: "wrong",
|
||||
err: authn.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "retrieve with user key",
|
||||
id: newKey.ID,
|
||||
issuer: userKey.Secret,
|
||||
err: authn.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "retrieve with reset key",
|
||||
id: newKey.ID,
|
||||
issuer: resetKey.Secret,
|
||||
err: authn.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
_, err := svc.Retrieve(context.Background(), tc.issuer, tc.id)
|
||||
assert.Equal(t, err, tc.err, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
func TestIdentify(t *testing.T) {
|
||||
svc := newService()
|
||||
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
|
||||
|
||||
resetKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.RecoveryKey, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
|
||||
|
||||
userKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute)})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
|
||||
|
||||
exp1 := time.Now().Add(-2 * time.Second)
|
||||
expKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now(), ExpiresAt: exp1})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing expired user key expected to succeed: %s", err))
|
||||
|
||||
invalidKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: 22, IssuedAt: time.Now()})
|
||||
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
key string
|
||||
id string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "identify login key",
|
||||
key: loginKey.Secret,
|
||||
id: email,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "identify reset key",
|
||||
key: resetKey.Secret,
|
||||
id: email,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "identify user key",
|
||||
key: userKey.Secret,
|
||||
id: email,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "identify expired user key",
|
||||
key: expKey.Secret,
|
||||
id: "",
|
||||
err: authn.ErrKeyExpired,
|
||||
},
|
||||
{
|
||||
desc: "identify expired key",
|
||||
key: invalidKey.Secret,
|
||||
id: "",
|
||||
err: authn.ErrUnauthorizedAccess,
|
||||
},
|
||||
{
|
||||
desc: "identify invalid key",
|
||||
key: "invalid",
|
||||
id: "",
|
||||
err: authn.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
id, err := svc.Identify(context.Background(), tc.key)
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.id, id, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.id, id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
swagger: "2.0"
|
||||
info:
|
||||
title: Mainflux authentication service
|
||||
description: HTTP API for managing platform API keys.
|
||||
version: "1.0.0"
|
||||
consumes:
|
||||
- "application/json"
|
||||
produces:
|
||||
- "application/json"
|
||||
paths:
|
||||
/keys:
|
||||
post:
|
||||
summary: Issue API key
|
||||
description: |
|
||||
Generates a new API key. Thew new API key will
|
||||
be uniquely identified by its ID.
|
||||
tags:
|
||||
- authn
|
||||
parameters:
|
||||
- name: key
|
||||
description: JSON-formatted document describing the new key.
|
||||
in: body
|
||||
schema:
|
||||
$ref: "#/definitions/Key"
|
||||
required: true
|
||||
responses:
|
||||
201:
|
||||
description: Issued new key.
|
||||
400:
|
||||
description: Failed due to malformed JSON.
|
||||
409:
|
||||
description: Failed due to using already existing ID.
|
||||
415:
|
||||
description: Missing or invalid content type.
|
||||
500:
|
||||
$ref: "#/responses/ServiceError"
|
||||
get:
|
||||
summary: Gets API key details.
|
||||
description: |
|
||||
Gets API key details for the given key.
|
||||
tags:
|
||||
- authn
|
||||
parameters:
|
||||
- $ref: "#/parameters/Authorization"
|
||||
- name: id
|
||||
description: API Key id.
|
||||
in: path
|
||||
type: string
|
||||
required: true
|
||||
responses:
|
||||
200:
|
||||
description: Data retrieved.
|
||||
schema:
|
||||
$ref: "#/definitions/Key"
|
||||
400:
|
||||
description: Failed due to malformed query parameters.
|
||||
403:
|
||||
description: Missing or invalid access token provided.
|
||||
500:
|
||||
$ref: "#/responses/ServiceError"
|
||||
delete:
|
||||
summary: Revoke API key
|
||||
description: |
|
||||
Revoke API key identified by the given ID.
|
||||
tags:
|
||||
- authn
|
||||
parameters:
|
||||
- $ref: "#/parameters/Authorization"
|
||||
- name: id
|
||||
description: API Key id.
|
||||
in: path
|
||||
type: string
|
||||
required: true
|
||||
responses:
|
||||
204:
|
||||
description: Key revoked.
|
||||
403:
|
||||
description: Missing or invalid access token provided.
|
||||
500:
|
||||
$ref: "#/responses/ServiceError"
|
||||
|
||||
definitions:
|
||||
Key:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "c5747f2f-2a7c-4fe1-b41a-51a5ae290945"
|
||||
description: API key unique identifier
|
||||
type:
|
||||
type: integer
|
||||
example: 0
|
||||
description: API key type. Keys of different type are processed differently
|
||||
issuer:
|
||||
type: string
|
||||
format: string
|
||||
example: "test@example.com"
|
||||
description: User's email or service identifier of API key issuer
|
||||
secret:
|
||||
type: string
|
||||
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZXhhbXBsZSIsImlhdCI6MTUxNjIzOTAyMn0.9UYAFWmPIn4ojss36LpIGSqABZHfADQmVuKQ4PJBMdI
|
||||
description: API Key value.
|
||||
issued_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2019-11-26 13:31:52"
|
||||
description: Time when the key is generated
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2019-11-26 13:31:52"
|
||||
description: Time when the Key expires
|
||||
required:
|
||||
- type
|
||||
|
||||
parameters:
|
||||
Authorization:
|
||||
name: Authorization
|
||||
description: Login key secret (User's access token).
|
||||
in: header
|
||||
type: string
|
||||
required: true
|
||||
|
||||
responses:
|
||||
ServiceError:
|
||||
description: Unexpected server-side error occurred.
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package authn
|
||||
|
||||
// Tokenizer specifies API for encoding and decoding between string and Key.
|
||||
type Tokenizer interface {
|
||||
// Issue converts API Key to its string representation.
|
||||
Issue(Key) (string, error)
|
||||
|
||||
// Parse extracts API Key data from string token.
|
||||
Parse(string) (Key, error)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package tracing contains middlewares that will add spans
|
||||
// to existing traces.
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
const (
|
||||
saveOp = "save"
|
||||
retrieveOp = "retrieve_by_id"
|
||||
revokeOp = "remove"
|
||||
)
|
||||
|
||||
var _ authn.KeyRepository = (*keyRepositoryMiddleware)(nil)
|
||||
|
||||
// keyRepositoryMiddleware tracks request and their latency, and adds spans
|
||||
// to context.
|
||||
type keyRepositoryMiddleware struct {
|
||||
tracer opentracing.Tracer
|
||||
repo authn.KeyRepository
|
||||
}
|
||||
|
||||
// New tracks request and their latency, and adds spans
|
||||
// to context.
|
||||
func New(repo authn.KeyRepository, tracer opentracing.Tracer) authn.KeyRepository {
|
||||
return keyRepositoryMiddleware{
|
||||
tracer: tracer,
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (krm keyRepositoryMiddleware) Save(ctx context.Context, key authn.Key) (string, error) {
|
||||
span := createSpan(ctx, krm.tracer, saveOp)
|
||||
defer span.Finish()
|
||||
ctx = opentracing.ContextWithSpan(ctx, span)
|
||||
|
||||
return krm.repo.Save(ctx, key)
|
||||
}
|
||||
|
||||
func (krm keyRepositoryMiddleware) Retrieve(ctx context.Context, owner, id string) (authn.Key, error) {
|
||||
span := createSpan(ctx, krm.tracer, retrieveOp)
|
||||
defer span.Finish()
|
||||
ctx = opentracing.ContextWithSpan(ctx, span)
|
||||
|
||||
return krm.repo.Retrieve(ctx, owner, id)
|
||||
}
|
||||
|
||||
func (krm keyRepositoryMiddleware) Remove(ctx context.Context, owner, id string) error {
|
||||
span := createSpan(ctx, krm.tracer, revokeOp)
|
||||
defer span.Finish()
|
||||
ctx = opentracing.ContextWithSpan(ctx, span)
|
||||
|
||||
return krm.repo.Remove(ctx, owner, id)
|
||||
}
|
||||
|
||||
func createSpan(ctx context.Context, tracer opentracing.Tracer, opName string) opentracing.Span {
|
||||
if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil {
|
||||
return tracer.StartSpan(
|
||||
opName,
|
||||
opentracing.ChildOf(parentSpan.Context()),
|
||||
)
|
||||
}
|
||||
|
||||
return tracer.StartSpan(opName)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package uuid provides a UUID identity provider.
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
)
|
||||
|
||||
var _ authn.IdentityProvider = (*uuidIdentityProvider)(nil)
|
||||
|
||||
type uuidIdentityProvider struct{}
|
||||
|
||||
// New instantiates a UUID identity provider.
|
||||
func New() authn.IdentityProvider {
|
||||
return &uuidIdentityProvider{}
|
||||
}
|
||||
|
||||
func (idp *uuidIdentityProvider) ID() (string, error) {
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return id.String(), nil
|
||||
}
|
||||
@@ -150,14 +150,14 @@ func dec(in []byte) ([]byte, error) {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func newService(users mainflux.UsersServiceClient, unknown map[string]string, url string) bootstrap.Service {
|
||||
func newService(authn mainflux.AuthNServiceClient, unknown map[string]string, url string) bootstrap.Service {
|
||||
things := mocks.NewConfigsRepository(unknown)
|
||||
config := mfsdk.Config{
|
||||
BaseURL: url,
|
||||
}
|
||||
|
||||
sdk := mfsdk.NewSDK(config)
|
||||
return bootstrap.New(users, things, sdk, encKey)
|
||||
return bootstrap.New(authn, things, sdk, encKey)
|
||||
}
|
||||
|
||||
func generateChannels() map[string]things.Channel {
|
||||
@@ -173,8 +173,8 @@ func generateChannels() map[string]things.Channel {
|
||||
return channels
|
||||
}
|
||||
|
||||
func newThingsService(users mainflux.UsersServiceClient) things.Service {
|
||||
return mocks.NewThingsService(map[string]things.Thing{}, generateChannels(), users)
|
||||
func newThingsService(authn mainflux.AuthNServiceClient) things.Service {
|
||||
return mocks.NewThingsService(map[string]things.Thing{}, generateChannels(), authn)
|
||||
}
|
||||
|
||||
func newThingsServer(svc things.Service) *httptest.Server {
|
||||
|
||||
@@ -19,17 +19,17 @@ type mainfluxThings struct {
|
||||
counter uint64
|
||||
things map[string]things.Thing
|
||||
channels map[string]things.Channel
|
||||
users mainflux.UsersServiceClient
|
||||
auth mainflux.AuthNServiceClient
|
||||
connections map[string][]string
|
||||
}
|
||||
|
||||
// NewThingsService returns Mainflux Things service mock.
|
||||
// Only methods used by SDK are mocked.
|
||||
func NewThingsService(things map[string]things.Thing, channels map[string]things.Channel, users mainflux.UsersServiceClient) things.Service {
|
||||
func NewThingsService(things map[string]things.Thing, channels map[string]things.Channel, authn mainflux.AuthNServiceClient) things.Service {
|
||||
return &mainfluxThings{
|
||||
things: things,
|
||||
channels: channels,
|
||||
users: users,
|
||||
auth: authn,
|
||||
connections: make(map[string][]string),
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func (svc *mainfluxThings) CreateThings(_ context.Context, owner string, ths ...
|
||||
svc.mu.Lock()
|
||||
defer svc.mu.Unlock()
|
||||
|
||||
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
if err != nil {
|
||||
return []things.Thing{}, things.ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func (svc *mainfluxThings) ViewThing(_ context.Context, owner, id string) (thing
|
||||
svc.mu.Lock()
|
||||
defer svc.mu.Unlock()
|
||||
|
||||
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
if err != nil {
|
||||
return things.Thing{}, things.ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func (svc *mainfluxThings) Connect(_ context.Context, owner string, chIDs, thIDs
|
||||
svc.mu.Lock()
|
||||
defer svc.mu.Unlock()
|
||||
|
||||
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
if err != nil {
|
||||
return things.ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -94,7 +94,7 @@ func (svc *mainfluxThings) Disconnect(_ context.Context, owner, chanID, thingID
|
||||
svc.mu.Lock()
|
||||
defer svc.mu.Unlock()
|
||||
|
||||
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
if err != nil || svc.channels[chanID].Owner != userID.Value {
|
||||
return things.ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func (svc *mainfluxThings) RemoveThing(_ context.Context, owner, id string) erro
|
||||
svc.mu.Lock()
|
||||
defer svc.mu.Unlock()
|
||||
|
||||
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
if err != nil {
|
||||
return things.ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -183,7 +183,7 @@ func (svc *mainfluxThings) CreateChannels(_ context.Context, owner string, chs .
|
||||
svc.mu.Lock()
|
||||
defer svc.mu.Unlock()
|
||||
|
||||
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
|
||||
if err != nil {
|
||||
return []things.Channel{}, things.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
@@ -11,20 +11,30 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var _ mainflux.UsersServiceClient = (*usersServiceMock)(nil)
|
||||
var _ mainflux.AuthNServiceClient = (*serviceMock)(nil)
|
||||
|
||||
type usersServiceMock struct {
|
||||
type serviceMock struct {
|
||||
users map[string]string
|
||||
}
|
||||
|
||||
// NewUsersService creates mock of users service.
|
||||
func NewUsersService(users map[string]string) mainflux.UsersServiceClient {
|
||||
return &usersServiceMock{users}
|
||||
func NewUsersService(users map[string]string) mainflux.AuthNServiceClient {
|
||||
return &serviceMock{users}
|
||||
}
|
||||
|
||||
func (svc usersServiceMock) Identify(ctx context.Context, in *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) {
|
||||
func (svc serviceMock) Identify(ctx context.Context, in *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) {
|
||||
if id, ok := svc.users[in.Value]; ok {
|
||||
return &mainflux.UserID{Value: id}, nil
|
||||
}
|
||||
return nil, users.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
func (svc serviceMock) Issue(ctx context.Context, in *mainflux.IssueReq, opts ...grpc.CallOption) (*mainflux.Token, error) {
|
||||
if id, ok := svc.users[in.GetIssuer()]; ok {
|
||||
switch in.Type {
|
||||
default:
|
||||
return &mainflux.Token{Value: id}, nil
|
||||
}
|
||||
}
|
||||
return nil, users.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
@@ -62,17 +62,17 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func newService(users mainflux.UsersServiceClient, url string) bootstrap.Service {
|
||||
func newService(auth mainflux.AuthNServiceClient, url string) bootstrap.Service {
|
||||
configs := mocks.NewConfigsRepository(map[string]string{unknownID: unknownKey})
|
||||
config := mfsdk.Config{
|
||||
BaseURL: url,
|
||||
}
|
||||
|
||||
sdk := mfsdk.NewSDK(config)
|
||||
return bootstrap.New(users, configs, sdk, encKey)
|
||||
return bootstrap.New(auth, configs, sdk, encKey)
|
||||
}
|
||||
|
||||
func newThingsService(users mainflux.UsersServiceClient) things.Service {
|
||||
func newThingsService(auth mainflux.AuthNServiceClient) things.Service {
|
||||
channels := make(map[string]things.Channel, channelsNum)
|
||||
for i := 0; i < channelsNum; i++ {
|
||||
id := strconv.Itoa(i + 1)
|
||||
@@ -83,7 +83,7 @@ func newThingsService(users mainflux.UsersServiceClient) things.Service {
|
||||
}
|
||||
}
|
||||
|
||||
return mocks.NewThingsService(map[string]things.Thing{}, channels, users)
|
||||
return mocks.NewThingsService(map[string]things.Thing{}, channels, auth)
|
||||
}
|
||||
|
||||
func newThingsServer(svc things.Service) *httptest.Server {
|
||||
|
||||
@@ -93,7 +93,7 @@ type ConfigReader interface {
|
||||
}
|
||||
|
||||
type bootstrapService struct {
|
||||
users mainflux.UsersServiceClient
|
||||
auth mainflux.AuthNServiceClient
|
||||
configs ConfigRepository
|
||||
sdk mfsdk.SDK
|
||||
encKey []byte
|
||||
@@ -101,11 +101,11 @@ type bootstrapService struct {
|
||||
}
|
||||
|
||||
// New returns new Bootstrap service.
|
||||
func New(users mainflux.UsersServiceClient, configs ConfigRepository, sdk mfsdk.SDK, encKey []byte) Service {
|
||||
func New(auth mainflux.AuthNServiceClient, configs ConfigRepository, sdk mfsdk.SDK, encKey []byte) Service {
|
||||
return &bootstrapService{
|
||||
configs: configs,
|
||||
sdk: sdk,
|
||||
users: users,
|
||||
auth: auth,
|
||||
encKey: encKey,
|
||||
}
|
||||
}
|
||||
@@ -340,7 +340,7 @@ func (bs bootstrapService) identify(token string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
res, err := bs.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := bs.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return "", ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
@@ -54,17 +54,17 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func newService(users mainflux.UsersServiceClient, url string) bootstrap.Service {
|
||||
func newService(auth mainflux.AuthNServiceClient, url string) bootstrap.Service {
|
||||
things := mocks.NewConfigsRepository(map[string]string{unknownID: unknownKey})
|
||||
config := mfsdk.Config{
|
||||
BaseURL: url,
|
||||
}
|
||||
|
||||
sdk := mfsdk.NewSDK(config)
|
||||
return bootstrap.New(users, things, sdk, encKey)
|
||||
return bootstrap.New(auth, things, sdk, encKey)
|
||||
}
|
||||
|
||||
func newThingsService(users mainflux.UsersServiceClient) things.Service {
|
||||
func newThingsService(auth mainflux.AuthNServiceClient) things.Service {
|
||||
channels := make(map[string]things.Channel, channelsNum)
|
||||
for i := 0; i < channelsNum; i++ {
|
||||
id := strconv.Itoa(i + 1)
|
||||
@@ -75,7 +75,7 @@ func newThingsService(users mainflux.UsersServiceClient) things.Service {
|
||||
}
|
||||
}
|
||||
|
||||
return mocks.NewThingsService(map[string]things.Thing{}, channels, users)
|
||||
return mocks.NewThingsService(map[string]things.Thing{}, channels, auth)
|
||||
}
|
||||
|
||||
func newThingsServer(svc things.Service) *httptest.Server {
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
api "github.com/mainflux/mainflux/authn/api"
|
||||
grpcapi "github.com/mainflux/mainflux/authn/api/grpc"
|
||||
httpapi "github.com/mainflux/mainflux/authn/api/http"
|
||||
"github.com/mainflux/mainflux/authn/jwt"
|
||||
"github.com/mainflux/mainflux/authn/postgres"
|
||||
"github.com/mainflux/mainflux/authn/tracing"
|
||||
mfidp "github.com/mainflux/mainflux/authn/uuid"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
jconfig "github.com/uber/jaeger-client-go/config"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
defLogLevel = "error"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "5432"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
defDBName = "authn"
|
||||
defDBSSLMode = "disable"
|
||||
defDBSSLCert = ""
|
||||
defDBSSLKey = ""
|
||||
defDBSSLRootCert = ""
|
||||
defHTTPPort = "8180"
|
||||
defGRPCPort = "8181"
|
||||
defSecret = "authn"
|
||||
defServerCert = ""
|
||||
defServerKey = ""
|
||||
defJaegerURL = ""
|
||||
|
||||
envLogLevel = "MF_AUTHN_LOG_LEVEL"
|
||||
envDBHost = "MF_AUTHN_DB_HOST"
|
||||
envDBPort = "MF_AUTHN_DB_PORT"
|
||||
envDBUser = "MF_AUTHN_DB_USER"
|
||||
envDBPass = "MF_AUTHN_DB_PASS"
|
||||
envDBName = "MF_AUTHN_DB"
|
||||
envDBSSLMode = "MF_AUTHN_DB_SSL_MODE"
|
||||
envDBSSLCert = "MF_AUTHN_DB_SSL_CERT"
|
||||
envDBSSLKey = "MF_AUTHN_DB_SSL_KEY"
|
||||
envDBSSLRootCert = "MF_AUTHN_DB_SSL_ROOT_CERT"
|
||||
envHTTPPort = "MF_AUTHN_HTTP_PORT"
|
||||
envGRPCPort = "MF_AUTHN_GRPC_PORT"
|
||||
envSecret = "MF_AUTHN_SECRET"
|
||||
envServerCert = "MF_AUTHN_SERVER_CERT"
|
||||
envServerKey = "MF_AUTHN_SERVER_KEY"
|
||||
envJaegerURL = "MF_JAEGER_URL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
logLevel string
|
||||
dbConfig postgres.Config
|
||||
httpPort string
|
||||
grpcPort string
|
||||
secret string
|
||||
serverCert string
|
||||
serverKey string
|
||||
jaegerURL string
|
||||
resetURL string
|
||||
}
|
||||
|
||||
type tokenConfig struct {
|
||||
hmacSampleSecret []byte // secret for signing token
|
||||
tokenDuration string // token in duration in min
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
db := connectToDB(cfg.dbConfig, logger)
|
||||
defer db.Close()
|
||||
|
||||
tracer, closer := initJaeger("authn", cfg.jaegerURL, logger)
|
||||
defer closer.Close()
|
||||
|
||||
dbTracer, dbCloser := initJaeger("authn_db", cfg.jaegerURL, logger)
|
||||
defer dbCloser.Close()
|
||||
|
||||
svc := newService(db, dbTracer, cfg.secret, logger)
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go startHTTPServer(tracer, svc, cfg.httpPort, cfg.serverCert, cfg.serverKey, logger, errs)
|
||||
go startGRPCServer(tracer, svc, cfg.grpcPort, cfg.serverCert, cfg.serverKey, logger, errs)
|
||||
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("Authentication service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
dbConfig := postgres.Config{
|
||||
Host: mainflux.Env(envDBHost, defDBHost),
|
||||
Port: mainflux.Env(envDBPort, defDBPort),
|
||||
User: mainflux.Env(envDBUser, defDBUser),
|
||||
Pass: mainflux.Env(envDBPass, defDBPass),
|
||||
Name: mainflux.Env(envDBName, defDBName),
|
||||
SSLMode: mainflux.Env(envDBSSLMode, defDBSSLMode),
|
||||
SSLCert: mainflux.Env(envDBSSLCert, defDBSSLCert),
|
||||
SSLKey: mainflux.Env(envDBSSLKey, defDBSSLKey),
|
||||
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
|
||||
}
|
||||
|
||||
return config{
|
||||
logLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
dbConfig: dbConfig,
|
||||
httpPort: mainflux.Env(envHTTPPort, defHTTPPort),
|
||||
grpcPort: mainflux.Env(envGRPCPort, defGRPCPort),
|
||||
secret: mainflux.Env(envSecret, defSecret),
|
||||
serverCert: mainflux.Env(envServerCert, defServerCert),
|
||||
serverKey: mainflux.Env(envServerKey, defServerKey),
|
||||
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer, io.Closer) {
|
||||
if url == "" {
|
||||
return opentracing.NoopTracer{}, ioutil.NopCloser(nil)
|
||||
}
|
||||
|
||||
tracer, closer, err := jconfig.Configuration{
|
||||
ServiceName: svcName,
|
||||
Sampler: &jconfig.SamplerConfig{
|
||||
Type: "const",
|
||||
Param: 1,
|
||||
},
|
||||
Reporter: &jconfig.ReporterConfig{
|
||||
LocalAgentHostPort: url,
|
||||
LogSpans: true,
|
||||
},
|
||||
}.NewTracer()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return tracer, closer
|
||||
}
|
||||
|
||||
func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB {
|
||||
db, err := postgres.Connect(dbConfig)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func newService(db *sqlx.DB, tracer opentracing.Tracer, secret string, logger logger.Logger) authn.Service {
|
||||
database := postgres.NewDatabase(db)
|
||||
repo := tracing.New(postgres.New(database), tracer)
|
||||
|
||||
idp := mfidp.New()
|
||||
t := jwt.New(secret)
|
||||
svc := authn.New(repo, idp, t)
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
svc = api.MetricsMiddleware(
|
||||
svc,
|
||||
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "authn",
|
||||
Subsystem: "api",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"}),
|
||||
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "authn",
|
||||
Subsystem: "api",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func startHTTPServer(tracer opentracing.Tracer, svc authn.Service, port string, certFile string, keyFile string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
if certFile != "" || keyFile != "" {
|
||||
logger.Info(fmt.Sprintf("Authentication service started using https, cert %s key %s, exposed port %s", certFile, keyFile, port))
|
||||
errs <- http.ListenAndServeTLS(p, certFile, keyFile, httpapi.MakeHandler(svc, tracer))
|
||||
return
|
||||
}
|
||||
logger.Info(fmt.Sprintf("Authentication service started using http, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, httpapi.MakeHandler(svc, tracer))
|
||||
|
||||
}
|
||||
|
||||
func startGRPCServer(tracer opentracing.Tracer, svc authn.Service, port string, certFile string, keyFile string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
listener, err := net.Listen("tcp", p)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to listen on port %s: %s", port, err))
|
||||
}
|
||||
|
||||
var server *grpc.Server
|
||||
if certFile != "" || keyFile != "" {
|
||||
creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to load authn certificates: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Info(fmt.Sprintf("Authentication gRPC service started using https on port %s with cert %s key %s", port, certFile, keyFile))
|
||||
server = grpc.NewServer(grpc.Creds(creds))
|
||||
} else {
|
||||
logger.Info(fmt.Sprintf("Authentication gRPC service started using http on port %s", port))
|
||||
server = grpc.NewServer()
|
||||
}
|
||||
|
||||
mainflux.RegisterAuthNServiceServer(server, grpcapi.NewServer(tracer, svc))
|
||||
logger.Info(fmt.Sprintf("Authentication gRPC service started, exposed port %s", port))
|
||||
errs <- server.Serve(listener)
|
||||
}
|
||||
+23
-22
@@ -16,6 +16,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
authapi "github.com/mainflux/mainflux/authn/api/grpc"
|
||||
rediscons "github.com/mainflux/mainflux/bootstrap/redis/consumer"
|
||||
redisprod "github.com/mainflux/mainflux/bootstrap/redis/producer"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
@@ -30,7 +31,6 @@ import (
|
||||
"github.com/mainflux/mainflux/bootstrap/postgres"
|
||||
mflog "github.com/mainflux/mainflux/logger"
|
||||
mfsdk "github.com/mainflux/mainflux/sdk/go"
|
||||
usersapi "github.com/mainflux/mainflux/users/api/grpc"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
jconfig "github.com/uber/jaeger-client-go/config"
|
||||
"google.golang.org/grpc"
|
||||
@@ -56,7 +56,6 @@ const (
|
||||
defServerKey = ""
|
||||
defBaseURL = "http://localhost"
|
||||
defThingsPrefix = ""
|
||||
defUsersURL = "localhost:8181"
|
||||
defThingsESURL = "localhost:6379"
|
||||
defThingsESPass = ""
|
||||
defThingsESDB = "0"
|
||||
@@ -65,7 +64,8 @@ const (
|
||||
defESDB = "0"
|
||||
defESConsumerName = "bootstrap"
|
||||
defJaegerURL = ""
|
||||
defUsersTimeout = "1" // in seconds
|
||||
defAuthURL = "localhost:8181"
|
||||
defAuthTimeout = "1" // in seconds
|
||||
|
||||
envLogLevel = "MF_BOOTSTRAP_LOG_LEVEL"
|
||||
envDBHost = "MF_BOOTSTRAP_DB_HOST"
|
||||
@@ -85,7 +85,6 @@ const (
|
||||
envServerKey = "MF_BOOTSTRAP_SERVER_KEY"
|
||||
envBaseURL = "MF_SDK_BASE_URL"
|
||||
envThingsPrefix = "MF_SDK_THINGS_PREFIX"
|
||||
envUsersURL = "MF_USERS_URL"
|
||||
envThingsESURL = "MF_THINGS_ES_URL"
|
||||
envThingsESPass = "MF_THINGS_ES_PASS"
|
||||
envThingsESDB = "MF_THINGS_ES_DB"
|
||||
@@ -94,7 +93,8 @@ const (
|
||||
envESDB = "MF_BOOTSTRAP_ES_DB"
|
||||
envESConsumerName = "MF_BOOTSTRAP_EVENT_CONSUMER"
|
||||
envJaegerURL = "MF_JAEGER_URL"
|
||||
envUsersTimeout = "MF_BOOTSTRAP_USERS_TIMEOUT"
|
||||
envAuthURL = "MF_AUTH_URL"
|
||||
envAuthTimeout = "MF_AUTH_TIMEOUT"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
@@ -108,7 +108,6 @@ type config struct {
|
||||
serverKey string
|
||||
baseURL string
|
||||
thingsPrefix string
|
||||
usersURL string
|
||||
esThingsURL string
|
||||
esThingsPass string
|
||||
esThingsDB string
|
||||
@@ -117,7 +116,8 @@ type config struct {
|
||||
esDB string
|
||||
esConsumerName string
|
||||
jaegerURL string
|
||||
usersTimeout time.Duration
|
||||
authURL string
|
||||
authTimeout time.Duration
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -131,19 +131,21 @@ func main() {
|
||||
db := connectToDB(cfg.dbConfig, logger)
|
||||
defer db.Close()
|
||||
|
||||
conn := connectToUsers(cfg, logger)
|
||||
defer conn.Close()
|
||||
|
||||
thingsESConn := connectToRedis(cfg.esThingsURL, cfg.esThingsPass, cfg.esThingsDB, logger)
|
||||
defer thingsESConn.Close()
|
||||
|
||||
esClient := connectToRedis(cfg.esURL, cfg.esPass, cfg.esDB, logger)
|
||||
defer esClient.Close()
|
||||
|
||||
usersTracer, usersCloser := initJaeger("users", cfg.jaegerURL, logger)
|
||||
defer usersCloser.Close()
|
||||
authTracer, authCloser := initJaeger("auth", cfg.jaegerURL, logger)
|
||||
defer authCloser.Close()
|
||||
|
||||
svc := newService(conn, usersTracer, db, logger, esClient, cfg)
|
||||
authConn := connectToAuth(cfg, logger)
|
||||
defer authConn.Close()
|
||||
|
||||
auth := authapi.NewClient(authTracer, authConn, cfg.authTimeout)
|
||||
|
||||
svc := newService(auth, db, logger, esClient, cfg)
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go startHTTPServer(svc, cfg, logger, errs)
|
||||
@@ -176,9 +178,9 @@ func loadConfig() config {
|
||||
SSLRootCert: mainflux.Env(envDBSSLRootCert, defDBSSLRootCert),
|
||||
}
|
||||
|
||||
timeout, err := strconv.ParseInt(mainflux.Env(envUsersTimeout, defUsersTimeout), 10, 64)
|
||||
timeout, err := strconv.ParseInt(mainflux.Env(envAuthTimeout, defAuthTimeout), 10, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid %s value: %s", envUsersTimeout, err.Error())
|
||||
log.Fatalf("Invalid %s value: %s", envAuthTimeout, err.Error())
|
||||
}
|
||||
encKey, err := hex.DecodeString(mainflux.Env(envEncryptKey, defEncryptKey))
|
||||
if err != nil {
|
||||
@@ -202,7 +204,6 @@ func loadConfig() config {
|
||||
serverKey: mainflux.Env(envServerKey, defServerKey),
|
||||
baseURL: mainflux.Env(envBaseURL, defBaseURL),
|
||||
thingsPrefix: mainflux.Env(envThingsPrefix, defThingsPrefix),
|
||||
usersURL: mainflux.Env(envUsersURL, defUsersURL),
|
||||
esThingsURL: mainflux.Env(envThingsESURL, defThingsESURL),
|
||||
esThingsPass: mainflux.Env(envThingsESPass, defThingsESPass),
|
||||
esThingsDB: mainflux.Env(envThingsESDB, defThingsESDB),
|
||||
@@ -211,7 +212,8 @@ func loadConfig() config {
|
||||
esDB: mainflux.Env(envESDB, defESDB),
|
||||
esConsumerName: mainflux.Env(envESConsumerName, defESConsumerName),
|
||||
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
|
||||
usersTimeout: time.Duration(timeout) * time.Second,
|
||||
authURL: mainflux.Env(envAuthURL, defAuthURL),
|
||||
authTimeout: time.Duration(timeout) * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +264,7 @@ func initJaeger(svcName, url string, logger logger.Logger) (opentracing.Tracer,
|
||||
return tracer, closer
|
||||
}
|
||||
|
||||
func newService(conn *grpc.ClientConn, usersTracer opentracing.Tracer, db *sqlx.DB, logger mflog.Logger, esClient *r.Client, cfg config) bootstrap.Service {
|
||||
func newService(auth mainflux.AuthNServiceClient, db *sqlx.DB, logger mflog.Logger, esClient *r.Client, cfg config) bootstrap.Service {
|
||||
thingsRepo := postgres.NewConfigRepository(db, logger)
|
||||
|
||||
config := mfsdk.Config{
|
||||
@@ -271,9 +273,8 @@ func newService(conn *grpc.ClientConn, usersTracer opentracing.Tracer, db *sqlx.
|
||||
}
|
||||
|
||||
sdk := mfsdk.NewSDK(config)
|
||||
users := usersapi.NewClient(usersTracer, conn, cfg.usersTimeout)
|
||||
|
||||
svc := bootstrap.New(users, thingsRepo, sdk, cfg.encKey)
|
||||
svc := bootstrap.New(auth, thingsRepo, sdk, cfg.encKey)
|
||||
svc = redisprod.NewEventStoreMiddleware(svc, esClient)
|
||||
svc = api.NewLoggingMiddleware(svc, logger)
|
||||
svc = api.MetricsMiddleware(
|
||||
@@ -294,7 +295,7 @@ func newService(conn *grpc.ClientConn, usersTracer opentracing.Tracer, db *sqlx.
|
||||
return svc
|
||||
}
|
||||
|
||||
func connectToUsers(cfg config, logger mflog.Logger) *grpc.ClientConn {
|
||||
func connectToAuth(cfg config, logger logger.Logger) *grpc.ClientConn {
|
||||
var opts []grpc.DialOption
|
||||
if cfg.clientTLS {
|
||||
if cfg.caCerts != "" {
|
||||
@@ -310,7 +311,7 @@ func connectToUsers(cfg config, logger mflog.Logger) *grpc.ClientConn {
|
||||
logger.Info("gRPC communication is not encrypted")
|
||||
}
|
||||
|
||||
conn, err := grpc.Dial(cfg.usersURL, opts...)
|
||||
conn, err := grpc.Dial(cfg.authURL, opts...)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to users service: %s", err))
|
||||
os.Exit(1)
|
||||
|
||||
+22
-22
@@ -25,6 +25,7 @@ import (
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/mainflux/mainflux"
|
||||
authapi "github.com/mainflux/mainflux/authn/api/grpc"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/things"
|
||||
"github.com/mainflux/mainflux/things/api"
|
||||
@@ -35,7 +36,6 @@ import (
|
||||
rediscache "github.com/mainflux/mainflux/things/redis"
|
||||
localusers "github.com/mainflux/mainflux/things/users"
|
||||
"github.com/mainflux/mainflux/things/uuid"
|
||||
usersapi "github.com/mainflux/mainflux/users/api/grpc"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
jconfig "github.com/uber/jaeger-client-go/config"
|
||||
"google.golang.org/grpc"
|
||||
@@ -65,11 +65,11 @@ const (
|
||||
defAuthGRPCPort = "8181"
|
||||
defServerCert = ""
|
||||
defServerKey = ""
|
||||
defUsersURL = "localhost:8181"
|
||||
defSingleUserEmail = ""
|
||||
defSingleUserToken = ""
|
||||
defJaegerURL = ""
|
||||
defUsersTimeout = "1" // in seconds
|
||||
defAuthURL = "localhost:8181"
|
||||
defAuthTimeout = "1" // in seconds
|
||||
|
||||
envLogLevel = "MF_THINGS_LOG_LEVEL"
|
||||
envDBHost = "MF_THINGS_DB_HOST"
|
||||
@@ -92,13 +92,13 @@ const (
|
||||
envHTTPPort = "MF_THINGS_HTTP_PORT"
|
||||
envAuthHTTPPort = "MF_THINGS_AUTH_HTTP_PORT"
|
||||
envAuthGRPCPort = "MF_THINGS_AUTH_GRPC_PORT"
|
||||
envUsersURL = "MF_USERS_URL"
|
||||
envServerCert = "MF_THINGS_SERVER_CERT"
|
||||
envServerKey = "MF_THINGS_SERVER_KEY"
|
||||
envSingleUserEmail = "MF_THINGS_SINGLE_USER_EMAIL"
|
||||
envSingleUserToken = "MF_THINGS_SINGLE_USER_TOKEN"
|
||||
envJaegerURL = "MF_JAEGER_URL"
|
||||
envUsersTimeout = "MF_THINGS_USERS_TIMEOUT"
|
||||
envAuthURL = "MF_AUTH_URL"
|
||||
envAuthTimeout = "MF_AUTH_TIMEOUT"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
@@ -115,13 +115,13 @@ type config struct {
|
||||
httpPort string
|
||||
authHTTPPort string
|
||||
authGRPCPort string
|
||||
usersURL string
|
||||
serverCert string
|
||||
serverKey string
|
||||
singleUserEmail string
|
||||
singleUserToken string
|
||||
jaegerURL string
|
||||
usersTimeout time.Duration
|
||||
authURL string
|
||||
authTimeout time.Duration
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -142,10 +142,10 @@ func main() {
|
||||
db := connectToDB(cfg.dbConfig, logger)
|
||||
defer db.Close()
|
||||
|
||||
usersTracer, usersCloser := initJaeger("users", cfg.jaegerURL, logger)
|
||||
defer usersCloser.Close()
|
||||
authTracer, authCloser := initJaeger("auth", cfg.jaegerURL, logger)
|
||||
defer authCloser.Close()
|
||||
|
||||
users, close := createUsersClient(cfg, usersTracer, logger)
|
||||
auth, close := createAuthClient(cfg, authTracer, logger)
|
||||
if close != nil {
|
||||
defer close()
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func main() {
|
||||
cacheTracer, cacheCloser := initJaeger("things_cache", cfg.jaegerURL, logger)
|
||||
defer cacheCloser.Close()
|
||||
|
||||
svc := newService(users, dbTracer, cacheTracer, db, cacheClient, esClient, logger)
|
||||
svc := newService(auth, dbTracer, cacheTracer, db, cacheClient, esClient, logger)
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go startHTTPServer(thhttpapi.MakeHandler(thingsTracer, svc), cfg.httpPort, cfg, logger, errs)
|
||||
@@ -179,9 +179,9 @@ func loadConfig() config {
|
||||
log.Fatalf("Invalid value passed for %s\n", envClientTLS)
|
||||
}
|
||||
|
||||
timeout, err := strconv.ParseInt(mainflux.Env(envUsersTimeout, defUsersTimeout), 10, 64)
|
||||
timeout, err := strconv.ParseInt(mainflux.Env(envAuthTimeout, defAuthTimeout), 10, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid %s value: %s", envUsersTimeout, err.Error())
|
||||
log.Fatalf("Invalid %s value: %s", envAuthTimeout, err.Error())
|
||||
}
|
||||
|
||||
dbConfig := postgres.Config{
|
||||
@@ -210,13 +210,13 @@ func loadConfig() config {
|
||||
httpPort: mainflux.Env(envHTTPPort, defHTTPPort),
|
||||
authHTTPPort: mainflux.Env(envAuthHTTPPort, defAuthHTTPPort),
|
||||
authGRPCPort: mainflux.Env(envAuthGRPCPort, defAuthGRPCPort),
|
||||
usersURL: mainflux.Env(envUsersURL, defUsersURL),
|
||||
serverCert: mainflux.Env(envServerCert, defServerCert),
|
||||
serverKey: mainflux.Env(envServerKey, defServerKey),
|
||||
singleUserEmail: mainflux.Env(envSingleUserEmail, defSingleUserEmail),
|
||||
singleUserToken: mainflux.Env(envSingleUserToken, defSingleUserToken),
|
||||
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
|
||||
usersTimeout: time.Duration(timeout) * time.Second,
|
||||
authURL: mainflux.Env(envAuthURL, defAuthURL),
|
||||
authTimeout: time.Duration(timeout) * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,16 +267,16 @@ func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
func createUsersClient(cfg config, tracer opentracing.Tracer, logger logger.Logger) (mainflux.UsersServiceClient, func() error) {
|
||||
func createAuthClient(cfg config, tracer opentracing.Tracer, logger logger.Logger) (mainflux.AuthNServiceClient, func() error) {
|
||||
if cfg.singleUserEmail != "" && cfg.singleUserToken != "" {
|
||||
return localusers.NewSingleUserService(cfg.singleUserEmail, cfg.singleUserToken), nil
|
||||
}
|
||||
|
||||
conn := connectToUsers(cfg, logger)
|
||||
return usersapi.NewClient(tracer, conn, cfg.usersTimeout), conn.Close
|
||||
conn := connectToAuth(cfg, logger)
|
||||
return authapi.NewClient(tracer, conn, cfg.authTimeout), conn.Close
|
||||
}
|
||||
|
||||
func connectToUsers(cfg config, logger logger.Logger) *grpc.ClientConn {
|
||||
func connectToAuth(cfg config, logger logger.Logger) *grpc.ClientConn {
|
||||
var opts []grpc.DialOption
|
||||
if cfg.clientTLS {
|
||||
if cfg.caCerts != "" {
|
||||
@@ -292,7 +292,7 @@ func connectToUsers(cfg config, logger logger.Logger) *grpc.ClientConn {
|
||||
logger.Info("gRPC communication is not encrypted")
|
||||
}
|
||||
|
||||
conn, err := grpc.Dial(cfg.usersURL, opts...)
|
||||
conn, err := grpc.Dial(cfg.authURL, opts...)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to users service: %s", err))
|
||||
os.Exit(1)
|
||||
@@ -301,7 +301,7 @@ func connectToUsers(cfg config, logger logger.Logger) *grpc.ClientConn {
|
||||
return conn
|
||||
}
|
||||
|
||||
func newService(users mainflux.UsersServiceClient, dbTracer opentracing.Tracer, cacheTracer opentracing.Tracer, db *sqlx.DB, cacheClient *redis.Client, esClient *redis.Client, logger logger.Logger) things.Service {
|
||||
func newService(auth mainflux.AuthNServiceClient, dbTracer opentracing.Tracer, cacheTracer opentracing.Tracer, db *sqlx.DB, cacheClient *redis.Client, esClient *redis.Client, logger logger.Logger) things.Service {
|
||||
database := postgres.NewDatabase(db)
|
||||
|
||||
thingsRepo := postgres.NewThingRepository(database)
|
||||
@@ -317,7 +317,7 @@ func newService(users mainflux.UsersServiceClient, dbTracer opentracing.Tracer,
|
||||
thingCache = tracing.ThingCacheMiddleware(cacheTracer, thingCache)
|
||||
idp := uuid.New()
|
||||
|
||||
svc := things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
svc := things.New(auth, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
svc = rediscache.NewEventStoreMiddleware(svc, esClient)
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
svc = api.MetricsMiddleware(
|
||||
|
||||
+94
-85
@@ -8,35 +8,31 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux/internal/email"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
"github.com/mainflux/mainflux/users/emailer"
|
||||
"github.com/mainflux/mainflux/users/token"
|
||||
"github.com/mainflux/mainflux/users/tracing"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/mainflux/mainflux"
|
||||
authapi "github.com/mainflux/mainflux/authn/api/grpc"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/users/api"
|
||||
grpcapi "github.com/mainflux/mainflux/users/api/grpc"
|
||||
httpapi "github.com/mainflux/mainflux/users/api/http"
|
||||
"github.com/mainflux/mainflux/users/bcrypt"
|
||||
"github.com/mainflux/mainflux/users/jwt"
|
||||
"github.com/mainflux/mainflux/users/postgres"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
jconfig "github.com/uber/jaeger-client-go/config"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -51,12 +47,17 @@ const (
|
||||
defDBSSLKey = ""
|
||||
defDBSSLRootCert = ""
|
||||
defHTTPPort = "8180"
|
||||
defGRPCPort = "8181"
|
||||
defSecret = "users"
|
||||
defServerCert = ""
|
||||
defServerKey = ""
|
||||
defJaegerURL = ""
|
||||
|
||||
defAuthnHTTPPort = "8989"
|
||||
defAuthnGRPCPort = "8181"
|
||||
defAuthnTimeout = "1" // in seconds
|
||||
defAuthnTLS = "false"
|
||||
defAuthnCACerts = ""
|
||||
defAuthnURL = "localhost:8181"
|
||||
|
||||
defEmailLogLevel = "debug"
|
||||
defEmailDriver = "smtp"
|
||||
defEmailHost = "localhost"
|
||||
@@ -67,8 +68,6 @@ const (
|
||||
defEmailFromName = ""
|
||||
defEmailTemplate = "email.tmpl"
|
||||
|
||||
defTokenSecret = "mainflux-secret"
|
||||
defTokenDuration = "5"
|
||||
defTokenResetEndpoint = "/reset-request" // URL where user lands after click on the reset link from email
|
||||
|
||||
envLogLevel = "MF_USERS_LOG_LEVEL"
|
||||
@@ -82,12 +81,17 @@ const (
|
||||
envDBSSLKey = "MF_USERS_DB_SSL_KEY"
|
||||
envDBSSLRootCert = "MF_USERS_DB_SSL_ROOT_CERT"
|
||||
envHTTPPort = "MF_USERS_HTTP_PORT"
|
||||
envGRPCPort = "MF_USERS_GRPC_PORT"
|
||||
envSecret = "MF_USERS_SECRET"
|
||||
envServerCert = "MF_USERS_SERVER_CERT"
|
||||
envServerKey = "MF_USERS_SERVER_KEY"
|
||||
envJaegerURL = "MF_JAEGER_URL"
|
||||
|
||||
envAuthnHTTPPort = "MF_AUTHN_HTTP_PORT"
|
||||
envAuthnGRPCPort = "MF_AUTHN_GRPC_PORT"
|
||||
envAuthnTimeout = "MF_AUTHN_TIMEOUT"
|
||||
envAuthnTLS = "MF_AUTHN_CLIENT_TLS"
|
||||
envAuthnCACerts = "MF_AUTHN_CA_CERTS"
|
||||
envAuthnURL = "MF_AUTHN_URL"
|
||||
|
||||
envEmailDriver = "MF_EMAIL_DRIVER"
|
||||
envEmailHost = "MF_EMAIL_HOST"
|
||||
envEmailPort = "MF_EMAIL_PORT"
|
||||
@@ -98,28 +102,24 @@ const (
|
||||
envEmailLogLevel = "MF_EMAIL_LOG_LEVEL"
|
||||
envEmailTemplate = "MF_EMAIL_TEMPLATE"
|
||||
|
||||
envTokenSecret = "MF_TOKEN_SECRET"
|
||||
envTokenDuration = "MF_TOKEN_DURATION"
|
||||
envTokenResetEndpoint = "MF_TOKEN_RESET_ENDPOINT"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
logLevel string
|
||||
dbConfig postgres.Config
|
||||
emailConf email.Config
|
||||
tokenConf tokenConfig
|
||||
httpPort string
|
||||
grpcPort string
|
||||
secret string
|
||||
serverCert string
|
||||
serverKey string
|
||||
jaegerURL string
|
||||
resetURL string
|
||||
}
|
||||
|
||||
type tokenConfig struct {
|
||||
hmacSampleSecret []byte // secret for signing token
|
||||
tokenDuration string // token in duration in min
|
||||
logLevel string
|
||||
dbConfig postgres.Config
|
||||
authnHTTPPort string
|
||||
authnGRPCPort string
|
||||
authnTimeout time.Duration
|
||||
authnTLS bool
|
||||
authnCACerts string
|
||||
authnURL string
|
||||
emailConf email.Config
|
||||
httpPort string
|
||||
serverCert string
|
||||
serverKey string
|
||||
jaegerURL string
|
||||
resetURL string
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -133,17 +133,24 @@ func main() {
|
||||
db := connectToDB(cfg.dbConfig, logger)
|
||||
defer db.Close()
|
||||
|
||||
authTracer, closer := initJaeger("auth", cfg.jaegerURL, logger)
|
||||
defer closer.Close()
|
||||
|
||||
auth, close := connectToAuthn(cfg, authTracer, logger)
|
||||
if close != nil {
|
||||
defer close()
|
||||
}
|
||||
|
||||
tracer, closer := initJaeger("users", cfg.jaegerURL, logger)
|
||||
defer closer.Close()
|
||||
|
||||
dbTracer, dbCloser := initJaeger("users_db", cfg.jaegerURL, logger)
|
||||
defer dbCloser.Close()
|
||||
|
||||
svc := newService(db, dbTracer, cfg, logger)
|
||||
svc := newService(db, dbTracer, auth, cfg, logger)
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go startHTTPServer(tracer, svc, cfg.httpPort, cfg.serverCert, cfg.serverKey, logger, errs)
|
||||
go startGRPCServer(tracer, svc, cfg.grpcPort, cfg.serverCert, cfg.serverKey, logger, errs)
|
||||
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
@@ -156,6 +163,16 @@ func main() {
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
timeout, err := strconv.ParseInt(mainflux.Env(envAuthnTimeout, defAuthnTimeout), 10, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid %s value: %s", envAuthnTimeout, err.Error())
|
||||
}
|
||||
|
||||
tls, err := strconv.ParseBool(mainflux.Env(envAuthnTLS, defAuthnTLS))
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid value passed for %s\n", envAuthnTLS)
|
||||
}
|
||||
|
||||
dbConfig := postgres.Config{
|
||||
Host: mainflux.Env(envDBHost, defDBHost),
|
||||
Port: mainflux.Env(envDBPort, defDBPort),
|
||||
@@ -179,23 +196,20 @@ func loadConfig() config {
|
||||
Template: mainflux.Env(envEmailTemplate, defEmailTemplate),
|
||||
}
|
||||
|
||||
tokenConf := tokenConfig{
|
||||
hmacSampleSecret: []byte(mainflux.Env(envTokenSecret, defTokenSecret)),
|
||||
tokenDuration: mainflux.Env(envTokenDuration, defTokenDuration),
|
||||
}
|
||||
|
||||
return config{
|
||||
logLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
dbConfig: dbConfig,
|
||||
emailConf: emailConf,
|
||||
tokenConf: tokenConf,
|
||||
httpPort: mainflux.Env(envHTTPPort, defHTTPPort),
|
||||
grpcPort: mainflux.Env(envGRPCPort, defGRPCPort),
|
||||
secret: mainflux.Env(envSecret, defSecret),
|
||||
serverCert: mainflux.Env(envServerCert, defServerCert),
|
||||
serverKey: mainflux.Env(envServerKey, defServerKey),
|
||||
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
|
||||
resetURL: mainflux.Env(envTokenResetEndpoint, defTokenResetEndpoint),
|
||||
logLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
dbConfig: dbConfig,
|
||||
authnHTTPPort: mainflux.Env(envAuthnHTTPPort, defAuthnHTTPPort),
|
||||
authnGRPCPort: mainflux.Env(envAuthnGRPCPort, defAuthnGRPCPort),
|
||||
authnURL: mainflux.Env(envAuthnURL, defAuthnURL),
|
||||
authnTimeout: time.Duration(timeout) * time.Second,
|
||||
authnTLS: tls,
|
||||
emailConf: emailConf,
|
||||
httpPort: mainflux.Env(envHTTPPort, defHTTPPort),
|
||||
serverCert: mainflux.Env(envServerCert, defServerCert),
|
||||
serverKey: mainflux.Env(envServerKey, defServerKey),
|
||||
jaegerURL: mainflux.Env(envJaegerURL, defJaegerURL),
|
||||
resetURL: mainflux.Env(envTokenResetEndpoint, defTokenResetEndpoint),
|
||||
}
|
||||
|
||||
}
|
||||
@@ -230,25 +244,45 @@ func connectToDB(dbConfig postgres.Config, logger logger.Logger) *sqlx.DB {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func newService(db *sqlx.DB, tracer opentracing.Tracer, c config, logger logger.Logger) users.Service {
|
||||
func connectToAuthn(cfg config, tracer opentracing.Tracer, logger logger.Logger) (mainflux.AuthNServiceClient, func() error) {
|
||||
var opts []grpc.DialOption
|
||||
if cfg.authnTLS {
|
||||
if cfg.authnCACerts != "" {
|
||||
tpc, err := credentials.NewClientTLSFromFile(cfg.authnCACerts, "")
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to create tls credentials: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
opts = append(opts, grpc.WithTransportCredentials(tpc))
|
||||
}
|
||||
} else {
|
||||
opts = append(opts, grpc.WithInsecure())
|
||||
logger.Info("gRPC communication is not encrypted")
|
||||
}
|
||||
|
||||
conn, err := grpc.Dial(cfg.authnURL, opts...)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to users service: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return authapi.NewClient(tracer, conn, cfg.authnTimeout), conn.Close
|
||||
}
|
||||
|
||||
func newService(db *sqlx.DB, tracer opentracing.Tracer, auth mainflux.AuthNServiceClient, c config, logger logger.Logger) users.Service {
|
||||
database := postgres.NewDatabase(db)
|
||||
repo := tracing.UserRepositoryMiddleware(postgres.New(database), tracer)
|
||||
hasher := bcrypt.New()
|
||||
idp := jwt.New(c.secret)
|
||||
emailer, err := emailer.New(c.resetURL, &c.emailConf)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to configure e-mailing util: %s", err.Error()))
|
||||
}
|
||||
tDur, err := strconv.Atoi(mainflux.Env(envTokenDuration, defTokenDuration))
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
tokenizer := token.New(c.tokenConf.hmacSampleSecret, tDur)
|
||||
|
||||
svc := users.New(repo, hasher, idp, emailer, tokenizer)
|
||||
svc := users.New(repo, hasher, auth, emailer)
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
svc = api.MetricsMiddleware(
|
||||
svc,
|
||||
@@ -265,6 +299,7 @@ func newService(db *sqlx.DB, tracer opentracing.Tracer, c config, logger logger.
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
@@ -272,35 +307,9 @@ func startHTTPServer(tracer opentracing.Tracer, svc users.Service, port string,
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
if certFile != "" || keyFile != "" {
|
||||
logger.Info(fmt.Sprintf("Users service started using https, cert %s key %s, exposed port %s", certFile, keyFile, port))
|
||||
errs <- http.ListenAndServeTLS(p, certFile, keyFile, httpapi.MakeHandler(svc, tracer, logger))
|
||||
errs <- http.ListenAndServeTLS(p, certFile, keyFile, api.MakeHandler(svc, tracer, logger))
|
||||
} else {
|
||||
logger.Info(fmt.Sprintf("Users service started using http, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, httpapi.MakeHandler(svc, tracer, logger))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(svc, tracer, logger))
|
||||
}
|
||||
}
|
||||
|
||||
func startGRPCServer(tracer opentracing.Tracer, svc users.Service, port string, certFile string, keyFile string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
listener, err := net.Listen("tcp", p)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to listen on port %s: %s", port, err))
|
||||
}
|
||||
|
||||
var server *grpc.Server
|
||||
if certFile != "" || keyFile != "" {
|
||||
creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to load users certificates: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Info(fmt.Sprintf("Users gRPC service started using https on port %s with cert %s key %s", port, certFile, keyFile))
|
||||
server = grpc.NewServer(grpc.Creds(creds))
|
||||
} else {
|
||||
logger.Info(fmt.Sprintf("Users gRPC service started using http on port %s", port))
|
||||
server = grpc.NewServer()
|
||||
}
|
||||
|
||||
mainflux.RegisterUsersServiceServer(server, grpcapi.NewServer(tracer, svc))
|
||||
logger.Info(fmt.Sprintf("Users gRPC service started, exposed port %s", port))
|
||||
errs <- server.Serve(listener)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ services:
|
||||
MF_BOOTSTRAP_DB_SSL_MODE: ${MF_BOOTSTRAP_DB_SSL_MODE}
|
||||
MF_BOOTSTRAP_PORT: ${MF_BOOTSTRAP_PORT}
|
||||
MF_SDK_BASE_URL: http://mainflux-things:${MF_THINGS_HTTP_PORT}
|
||||
MF_USERS_URL: mainflux-users:${MF_USERS_GRPC_PORT}
|
||||
MF_AUTH_URL: authn:${MF_AUTHN_GRPC_PORT}
|
||||
MF_THINGS_ES_URL: es-redis:${MF_REDIS_TCP_PORT}
|
||||
MF_BOOTSTRAP_ES_URL: es-redis:${MF_REDIS_TCP_PORT}
|
||||
MF_JAEGER_URL: ${MF_JAEGER_URL}
|
||||
|
||||
@@ -8,6 +8,7 @@ networks:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mainflux-authn-db-volume:
|
||||
mainflux-users-db-volume:
|
||||
mainflux-things-db-volume:
|
||||
mainflux-things-redis-volume:
|
||||
@@ -50,6 +51,44 @@ services:
|
||||
networks:
|
||||
- mainflux-base-net
|
||||
|
||||
authn-db:
|
||||
image: postgres:10.8-alpine
|
||||
container_name: mainflux-authn-db
|
||||
restart: on-failure
|
||||
environment:
|
||||
POSTGRES_USER: ${MF_AUTHN_DB_USER}
|
||||
POSTGRES_PASSWORD: ${MF_AUTHN_DB_PASS}
|
||||
POSTGRES_DB: ${MF_AUTHN_DB}
|
||||
networks:
|
||||
- mainflux-base-net
|
||||
volumes:
|
||||
- mainflux-authn-db-volume:/var/lib/postgresql/data
|
||||
|
||||
authn:
|
||||
image: mainflux/authn:latest
|
||||
container_name: mainflux-authn
|
||||
depends_on:
|
||||
- authn-db
|
||||
expose:
|
||||
- ${MF_AUTHN_GRPC_PORT}
|
||||
restart: on-failure
|
||||
environment:
|
||||
MF_AUTHN_LOG_LEVEL: ${MF_AUTHN_LOG_LEVEL}
|
||||
MF_AUTHN_DB_HOST: authn-db
|
||||
MF_AUTHN_DB_PORT: ${MF_AUTHN_DB_PORT}
|
||||
MF_AUTHN_DB_USER: ${MF_AUTHN_DB_USER}
|
||||
MF_AUTHN_DB_PASS: ${MF_AUTHN_DB_PASS}
|
||||
MF_AUTHN_DB: ${MF_AUTHN_DB}
|
||||
MF_AUTHN_HTTP_PORT: ${MF_AUTHN_HTTP_PORT}
|
||||
MF_AUTHN_GRPC_PORT: ${MF_AUTHN_GRPC_PORT}
|
||||
MF_AUTHN_SECRET: ${MF_AUTHN_SECRET}
|
||||
MF_JAEGER_URL: ${MF_JAEGER_URL}
|
||||
ports:
|
||||
- ${MF_AUTHN_HTTP_PORT}:${MF_AUTHN_HTTP_PORT}
|
||||
- ${MF_AUTHN_GRPC_PORT}:${MF_AUTHN_GRPC_PORT}
|
||||
networks:
|
||||
- mainflux-base-net
|
||||
|
||||
users-db:
|
||||
image: postgres:10.8-alpine
|
||||
container_name: mainflux-users-db
|
||||
@@ -70,8 +109,7 @@ services:
|
||||
- ./users/emailer/templates/${MF_EMAIL_TEMPLATE}:/${MF_EMAIL_TEMPLATE}
|
||||
depends_on:
|
||||
- users-db
|
||||
expose:
|
||||
- ${MF_USERS_GRPC_PORT}
|
||||
- authn
|
||||
restart: on-failure
|
||||
environment:
|
||||
MF_USERS_LOG_LEVEL: ${MF_USERS_LOG_LEVEL}
|
||||
@@ -81,8 +119,6 @@ services:
|
||||
MF_USERS_DB_PASS: ${MF_USERS_DB_PASS}
|
||||
MF_USERS_DB: ${MF_USERS_DB}
|
||||
MF_USERS_HTTP_PORT: ${MF_USERS_HTTP_PORT}
|
||||
MF_USERS_GRPC_PORT: ${MF_USERS_GRPC_PORT}
|
||||
MF_USERS_SECRET: ${MF_USERS_SECRET}
|
||||
MF_JAEGER_URL: ${MF_JAEGER_URL}
|
||||
MF_EMAIL_DRIVER: ${MF_EMAIL_DRIVER}
|
||||
MF_EMAIL_HOST: ${MF_EMAIL_HOST}
|
||||
@@ -92,9 +128,8 @@ services:
|
||||
MF_EMAIL_FROM_ADDRESS: ${MF_EMAIL_FROM_ADDRESS}
|
||||
MF_EMAIL_FROM_NAME: ${MF_EMAIL_FROM_NAME}
|
||||
MF_EMAIL_TEMPLATE: ${MF_EMAIL_TEMPLATE}
|
||||
MF_TOKEN_SECRET: ${MF_TOKEN_SECRET}
|
||||
MF_TOKEN_DURATION: ${MF_TOKEN_DURATION}
|
||||
MF_TOKEN_RESET_ENDPOINT: ${MF_TOKEN_RESET_ENDPOINT}
|
||||
MF_AUTHN_URL: authn:${MF_AUTHN_GRPC_PORT}
|
||||
ports:
|
||||
- ${MF_USERS_HTTP_PORT}:${MF_USERS_HTTP_PORT}
|
||||
networks:
|
||||
@@ -127,7 +162,7 @@ services:
|
||||
container_name: mainflux-things
|
||||
depends_on:
|
||||
- things-db
|
||||
- users
|
||||
- authn
|
||||
restart: on-failure
|
||||
environment:
|
||||
MF_THINGS_LOG_LEVEL: ${MF_THINGS_LOG_LEVEL}
|
||||
@@ -141,7 +176,7 @@ services:
|
||||
MF_THINGS_HTTP_PORT: ${MF_THINGS_HTTP_PORT}
|
||||
MF_THINGS_AUTH_HTTP_PORT: ${MF_THINGS_AUTH_HTTP_PORT}
|
||||
MF_THINGS_AUTH_GRPC_PORT: ${MF_THINGS_AUTH_GRPC_PORT}
|
||||
MF_USERS_URL: users:${MF_USERS_GRPC_PORT}
|
||||
MF_AUTH_URL: authn:${MF_AUTHN_GRPC_PORT}
|
||||
MF_THINGS_SECRET: ${MF_THINGS_SECRET}
|
||||
MF_JAEGER_URL: ${MF_JAEGER_URL}
|
||||
ports:
|
||||
|
||||
@@ -7,7 +7,9 @@ require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/Microsoft/go-winio v0.4.7 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/VividCortex/gohistogram v1.0.0 // indirect
|
||||
github.com/cenkalti/backoff v2.0.0+incompatible // indirect
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
|
||||
github.com/containerd/continuity v0.0.0-20180416230128-c6cef3483023 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/docker/docker v1.13.1
|
||||
@@ -36,6 +38,8 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.3 // indirect
|
||||
github.com/nats-io/go-nats v1.6.0
|
||||
github.com/nats-io/nuid v1.0.0 // indirect
|
||||
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||
github.com/onsi/gomega v1.7.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v0.1.1 // indirect
|
||||
|
||||
@@ -8,6 +8,7 @@ github.com/Microsoft/go-winio v0.4.7/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyv
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -21,6 +22,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
|
||||
github.com/cisco/senml v0.0.0-20181031221301-910a55054e16 h1:gVMpF6unIWOG8JgCt3XhlYdT3lRFDcMMVJdMesU+TQY=
|
||||
github.com/cisco/senml v0.0.0-20181031221301-910a55054e16/go.mod h1:pLFASTQEf1nGfvoMwxmOjLeImwMKQMx18w38UcI9ZfI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/containerd/continuity v0.0.0-20180416230128-c6cef3483023 h1:ydDbSX89iFHufaVN8xlS22aWpajSFfmXL+fQNWnhrIg=
|
||||
github.com/containerd/continuity v0.0.0-20180416230128-c6cef3483023/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
@@ -105,6 +107,7 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20180920040306-f579f869bbfe h1:MCgzztuoH5LZNr9AkIaicIDvCfACu11KUCCZQnRHDC0=
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20180920040306-f579f869bbfe/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb v1.6.4 h1:K8wPlkrP02HzHTJbbUQQ1CZ2Hw6LtpG4xbNEgnlhMZU=
|
||||
@@ -152,6 +155,9 @@ github.com/nats-io/go-nats v1.6.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1
|
||||
github.com/nats-io/nuid v1.0.0 h1:44QGdhbiANq8ZCbUkdn6W5bqtg+mHuDE4wOUuxxndFs=
|
||||
github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
@@ -259,6 +265,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180509002218-f73e4c9ed3b7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -274,6 +281,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sys v0.0.0-20180510032850-7dfd1290c791/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -306,6 +314,7 @@ google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRn
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gorp.v1 v1.7.1 h1:GBB9KrWRATQZh95HJyVGUZrWwOPswitEYEyqlK8JbAA=
|
||||
gopkg.in/gorp.v1 v1.7.1/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
@@ -313,6 +322,7 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ory-am/dockertest.v3 v3.3.2 h1:NgIHJacfXajJResc7luKYPF/F2kul6MXqbleEjv4PAY=
|
||||
gopkg.in/ory-am/dockertest.v3 v3.3.2/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKekYC6CovU+ek=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
|
||||
@@ -82,10 +82,6 @@ spec:
|
||||
value: "mainflux-users-postgres.default"
|
||||
- name: MF_USERS_HTTP_PORT
|
||||
value: "8180"
|
||||
- name: MF_USERS_GRPC_PORT
|
||||
value: "8181"
|
||||
- name: MF_USERS_SECRET
|
||||
value: "test-secret"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /version
|
||||
|
||||
@@ -40,7 +40,7 @@ var (
|
||||
)
|
||||
|
||||
func newThingsService(tokens map[string]string) things.Service {
|
||||
users := mocks.NewUsersService(tokens)
|
||||
auth := mocks.NewAuthService(tokens)
|
||||
conns := make(chan mocks.Connection)
|
||||
thingsRepo := mocks.NewThingRepository(conns)
|
||||
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
|
||||
@@ -48,7 +48,7 @@ func newThingsService(tokens map[string]string) things.Service {
|
||||
thingCache := mocks.NewThingCache()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
|
||||
return things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
return things.New(auth, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
}
|
||||
|
||||
func newThingsServer(svc things.Service) *httptest.Server {
|
||||
|
||||
+10
-8
@@ -4,18 +4,19 @@
|
||||
package sdk_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
sdk "github.com/mainflux/mainflux/sdk/go"
|
||||
"github.com/opentracing/opentracing-go/mocktracer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
httpapi "github.com/mainflux/mainflux/users/api/http"
|
||||
"github.com/mainflux/mainflux/users/jwt"
|
||||
"github.com/mainflux/mainflux/users/api"
|
||||
|
||||
"github.com/mainflux/mainflux/users"
|
||||
"github.com/mainflux/mainflux/users/mocks"
|
||||
@@ -28,16 +29,16 @@ const (
|
||||
func newUserService() users.Service {
|
||||
repo := mocks.NewUserRepository()
|
||||
hasher := mocks.NewHasher()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
tok := mocks.NewTokenizer()
|
||||
auth := mocks.NewAuthService(map[string]string{"user@example.com": "user@example.com"})
|
||||
|
||||
emailer := mocks.NewEmailer()
|
||||
|
||||
return users.New(repo, hasher, idp, emailer, tok)
|
||||
return users.New(repo, hasher, auth, emailer)
|
||||
}
|
||||
|
||||
func newUserServer(svc users.Service) *httptest.Server {
|
||||
logger, _ := log.New(os.Stdout, log.Info.String())
|
||||
mux := httpapi.MakeHandler(svc, mocktracer.New(), logger)
|
||||
mux := api.MakeHandler(svc, mocktracer.New(), logger)
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
@@ -119,8 +120,9 @@ func TestCreateToken(t *testing.T) {
|
||||
|
||||
mainfluxSDK := sdk.NewSDK(sdkConf)
|
||||
user := sdk.User{Email: "user@example.com", Password: "password"}
|
||||
j := jwt.New("secret")
|
||||
token, _ := j.TemporaryKey(user.Email)
|
||||
auth := mocks.NewAuthService(map[string]string{user.Email: user.Email})
|
||||
tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Issuer: user.Email, Type: 0})
|
||||
token := tkn.GetValue()
|
||||
mainfluxSDK.CreateUser(user)
|
||||
cases := []struct {
|
||||
desc string
|
||||
|
||||
@@ -41,7 +41,7 @@ func startServer() {
|
||||
}
|
||||
|
||||
func newService(tokens map[string]string) things.Service {
|
||||
users := mocks.NewUsersService(tokens)
|
||||
auth := mocks.NewAuthService(tokens)
|
||||
conns := make(chan mocks.Connection)
|
||||
thingsRepo := mocks.NewThingRepository(conns)
|
||||
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
|
||||
@@ -49,5 +49,5 @@ func newService(tokens map[string]string) things.Service {
|
||||
thingCache := mocks.NewThingCache()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
|
||||
return things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
return things.New(auth, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func toJSON(data interface{}) string {
|
||||
}
|
||||
|
||||
func newService(tokens map[string]string) things.Service {
|
||||
users := mocks.NewUsersService(tokens)
|
||||
auth := mocks.NewAuthService(tokens)
|
||||
conns := make(chan mocks.Connection)
|
||||
thingsRepo := mocks.NewThingRepository(conns)
|
||||
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
|
||||
@@ -74,7 +74,7 @@ func newService(tokens map[string]string) things.Service {
|
||||
thingCache := mocks.NewThingCache()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
|
||||
return things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
return things.New(auth, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
}
|
||||
|
||||
func newServer(svc things.Service) *httptest.Server {
|
||||
|
||||
@@ -69,7 +69,7 @@ func (tr testRequest) make() (*http.Response, error) {
|
||||
}
|
||||
|
||||
func newService(tokens map[string]string) things.Service {
|
||||
users := mocks.NewUsersService(tokens)
|
||||
auth := mocks.NewAuthService(tokens)
|
||||
conns := make(chan mocks.Connection)
|
||||
thingsRepo := mocks.NewThingRepository(conns)
|
||||
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
|
||||
@@ -77,7 +77,7 @@ func newService(tokens map[string]string) things.Service {
|
||||
thingCache := mocks.NewThingCache()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
|
||||
return things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
return things.New(auth, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
}
|
||||
|
||||
func newServer(svc things.Service) *httptest.Server {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var _ mainflux.AuthNServiceClient = (*authServiceMock)(nil)
|
||||
|
||||
type authServiceMock struct {
|
||||
users map[string]string
|
||||
}
|
||||
|
||||
// NewAuthService creates mock of users service.
|
||||
func NewAuthService(users map[string]string) mainflux.AuthNServiceClient {
|
||||
return &authServiceMock{users}
|
||||
}
|
||||
|
||||
func (svc authServiceMock) Identify(ctx context.Context, in *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) {
|
||||
if id, ok := svc.users[in.Value]; ok {
|
||||
return &mainflux.UserID{Value: id}, nil
|
||||
}
|
||||
return nil, users.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
func (svc authServiceMock) Issue(ctx context.Context, in *mainflux.IssueReq, opts ...grpc.CallOption) (*mainflux.Token, error) {
|
||||
if id, ok := svc.users[in.GetIssuer()]; ok {
|
||||
switch in.Type {
|
||||
default:
|
||||
return &mainflux.Token{Value: id}, nil
|
||||
}
|
||||
}
|
||||
return nil, users.ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var _ mainflux.UsersServiceClient = (*usersServiceMock)(nil)
|
||||
|
||||
type usersServiceMock struct {
|
||||
users map[string]string
|
||||
}
|
||||
|
||||
// NewUsersService creates mock of users service.
|
||||
func NewUsersService(users map[string]string) mainflux.UsersServiceClient {
|
||||
return &usersServiceMock{users}
|
||||
}
|
||||
|
||||
func (svc usersServiceMock) Identify(ctx context.Context, in *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) {
|
||||
if id, ok := svc.users[in.Value]; ok {
|
||||
return &mainflux.UserID{Value: id}, nil
|
||||
}
|
||||
return nil, users.ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -37,7 +37,7 @@ const (
|
||||
)
|
||||
|
||||
func newService(tokens map[string]string) things.Service {
|
||||
users := mocks.NewUsersService(tokens)
|
||||
auth := mocks.NewAuthService(tokens)
|
||||
conns := make(chan mocks.Connection)
|
||||
thingsRepo := mocks.NewThingRepository(conns)
|
||||
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
|
||||
@@ -45,7 +45,7 @@ func newService(tokens map[string]string) things.Service {
|
||||
thingCache := mocks.NewThingCache()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
|
||||
return things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
return things.New(auth, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
}
|
||||
|
||||
func TestCreateThings(t *testing.T) {
|
||||
|
||||
+19
-18
@@ -114,7 +114,7 @@ type PageMetadata struct {
|
||||
var _ Service = (*thingsService)(nil)
|
||||
|
||||
type thingsService struct {
|
||||
users mainflux.UsersServiceClient
|
||||
auth mainflux.AuthNServiceClient
|
||||
things ThingRepository
|
||||
channels ChannelRepository
|
||||
channelCache ChannelCache
|
||||
@@ -123,9 +123,9 @@ type thingsService struct {
|
||||
}
|
||||
|
||||
// New instantiates the things service implementation.
|
||||
func New(users mainflux.UsersServiceClient, things ThingRepository, channels ChannelRepository, ccache ChannelCache, tcache ThingCache, idp IdentityProvider) Service {
|
||||
func New(auth mainflux.AuthNServiceClient, things ThingRepository, channels ChannelRepository, ccache ChannelCache, tcache ThingCache, idp IdentityProvider) Service {
|
||||
return &thingsService{
|
||||
users: users,
|
||||
auth: auth,
|
||||
things: things,
|
||||
channels: channels,
|
||||
channelCache: ccache,
|
||||
@@ -133,8 +133,9 @@ func New(users mainflux.UsersServiceClient, things ThingRepository, channels Cha
|
||||
idp: idp,
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *thingsService) CreateThings(ctx context.Context, token string, things ...Thing) ([]Thing, error) {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return []Thing{}, ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -159,7 +160,7 @@ func (ts *thingsService) CreateThings(ctx context.Context, token string, things
|
||||
}
|
||||
|
||||
func (ts *thingsService) UpdateThing(ctx context.Context, token string, thing Thing) error {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -170,7 +171,7 @@ func (ts *thingsService) UpdateThing(ctx context.Context, token string, thing Th
|
||||
}
|
||||
|
||||
func (ts *thingsService) UpdateKey(ctx context.Context, token, id, key string) error {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -182,7 +183,7 @@ func (ts *thingsService) UpdateKey(ctx context.Context, token, id, key string) e
|
||||
}
|
||||
|
||||
func (ts *thingsService) ViewThing(ctx context.Context, token, id string) (Thing, error) {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return Thing{}, ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -191,7 +192,7 @@ func (ts *thingsService) ViewThing(ctx context.Context, token, id string) (Thing
|
||||
}
|
||||
|
||||
func (ts *thingsService) ListThings(ctx context.Context, token string, offset, limit uint64, name string, metadata Metadata) (ThingsPage, error) {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ThingsPage{}, ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -200,7 +201,7 @@ func (ts *thingsService) ListThings(ctx context.Context, token string, offset, l
|
||||
}
|
||||
|
||||
func (ts *thingsService) ListThingsByChannel(ctx context.Context, token, channel string, offset, limit uint64) (ThingsPage, error) {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ThingsPage{}, ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -209,7 +210,7 @@ func (ts *thingsService) ListThingsByChannel(ctx context.Context, token, channel
|
||||
}
|
||||
|
||||
func (ts *thingsService) RemoveThing(ctx context.Context, token, id string) error {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -219,7 +220,7 @@ func (ts *thingsService) RemoveThing(ctx context.Context, token, id string) erro
|
||||
}
|
||||
|
||||
func (ts *thingsService) CreateChannels(ctx context.Context, token string, channels ...Channel) ([]Channel, error) {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return []Channel{}, ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -237,7 +238,7 @@ func (ts *thingsService) CreateChannels(ctx context.Context, token string, chann
|
||||
}
|
||||
|
||||
func (ts *thingsService) UpdateChannel(ctx context.Context, token string, channel Channel) error {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -247,7 +248,7 @@ func (ts *thingsService) UpdateChannel(ctx context.Context, token string, channe
|
||||
}
|
||||
|
||||
func (ts *thingsService) ViewChannel(ctx context.Context, token, id string) (Channel, error) {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return Channel{}, ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -256,7 +257,7 @@ func (ts *thingsService) ViewChannel(ctx context.Context, token, id string) (Cha
|
||||
}
|
||||
|
||||
func (ts *thingsService) ListChannels(ctx context.Context, token string, offset, limit uint64, name string, m Metadata) (ChannelsPage, error) {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ChannelsPage{}, ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -265,7 +266,7 @@ func (ts *thingsService) ListChannels(ctx context.Context, token string, offset,
|
||||
}
|
||||
|
||||
func (ts *thingsService) ListChannelsByThing(ctx context.Context, token, thing string, offset, limit uint64) (ChannelsPage, error) {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ChannelsPage{}, ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -274,7 +275,7 @@ func (ts *thingsService) ListChannelsByThing(ctx context.Context, token, thing s
|
||||
}
|
||||
|
||||
func (ts *thingsService) RemoveChannel(ctx context.Context, token, id string) error {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -284,7 +285,7 @@ func (ts *thingsService) RemoveChannel(ctx context.Context, token, id string) er
|
||||
}
|
||||
|
||||
func (ts *thingsService) Connect(ctx context.Context, token string, chIDs, thIDs []string) error {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -293,7 +294,7 @@ func (ts *thingsService) Connect(ctx context.Context, token string, chIDs, thIDs
|
||||
}
|
||||
|
||||
func (ts *thingsService) Disconnect(ctx context.Context, token, chanID, thingID string) error {
|
||||
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
|
||||
res, err := ts.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ var (
|
||||
)
|
||||
|
||||
func newService(tokens map[string]string) things.Service {
|
||||
users := mocks.NewUsersService(tokens)
|
||||
auth := mocks.NewAuthService(tokens)
|
||||
conns := make(chan mocks.Connection)
|
||||
thingsRepo := mocks.NewThingRepository(conns)
|
||||
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
|
||||
@@ -36,7 +36,7 @@ func newService(tokens map[string]string) things.Service {
|
||||
thingCache := mocks.NewThingCache()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
|
||||
return things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
return things.New(auth, thingsRepo, channelsRepo, chanCache, thingCache, idp)
|
||||
}
|
||||
|
||||
func TestCreateThings(t *testing.T) {
|
||||
|
||||
+14
-3
@@ -15,21 +15,32 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var _ mainflux.UsersServiceClient = (*singleUserRepo)(nil)
|
||||
var _ mainflux.AuthNServiceClient = (*singleUserRepo)(nil)
|
||||
|
||||
type singleUserRepo struct {
|
||||
email string
|
||||
token string
|
||||
}
|
||||
|
||||
// NewSingleUserService creates single user repository for constraind environments.
|
||||
func NewSingleUserService(email, token string) mainflux.UsersServiceClient {
|
||||
// NewSingleUserService creates single user repository for constrained environments.
|
||||
func NewSingleUserService(email, token string) mainflux.AuthNServiceClient {
|
||||
return singleUserRepo{
|
||||
email: email,
|
||||
token: token,
|
||||
}
|
||||
}
|
||||
|
||||
func (repo singleUserRepo) Issue(ctx context.Context, req *mainflux.IssueReq, opts ...grpc.CallOption) (*mainflux.Token, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second)
|
||||
defer cancel()
|
||||
|
||||
if repo.token != req.GetIssuer() {
|
||||
return nil, things.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
return &mainflux.Token{Value: repo.token}, nil
|
||||
}
|
||||
|
||||
func (repo singleUserRepo) Identify(ctx context.Context, token *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -45,3 +45,30 @@ func TestIdentify(t *testing.T) {
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s, got %s", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue(t *testing.T) {
|
||||
svc := users.NewSingleUserService(email, token)
|
||||
|
||||
cases := map[string]struct {
|
||||
token string
|
||||
id string
|
||||
err error
|
||||
}{
|
||||
"issue key unauthorized": {
|
||||
token: "non-existing",
|
||||
id: "",
|
||||
err: things.ErrUnauthorizedAccess,
|
||||
},
|
||||
"issue key": {
|
||||
token: token,
|
||||
id: token,
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
id, err := svc.Issue(context.Background(), &mainflux.IssueReq{Issuer: tc.token, Type: 0})
|
||||
assert.Equal(t, tc.id, id.GetValue(), fmt.Sprintf("%s: expected %s, got %s", desc, tc.id, id.GetValue()))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s, got %s", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
+1
-9
@@ -29,10 +29,8 @@ default values.
|
||||
| MF_USERS_DB_SSL_KEY | Path to the PEM encoded key file | |
|
||||
| MF_USERS_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | |
|
||||
| MF_USERS_HTTP_PORT | Users service HTTP port | 8180 |
|
||||
| MF_USERS_GRPC_PORT | Users service gRPC port | 8181 |
|
||||
| MF_USERS_SERVER_CERT | Path to server certificate in pem format | |
|
||||
| MF_USERS_SERVER_KEY | Path to server key in pem format | |
|
||||
| MF_USERS_SECRET | String used for signing tokens | users |
|
||||
| MF_JAEGER_URL | Jaeger server URL | localhost:6831 |
|
||||
| MF_EMAIL_DRIVER | Mail server driver, mail server for sending reset password token | smtp |
|
||||
| MF_EMAIL_HOST | Mail server host | localhost |
|
||||
@@ -42,8 +40,6 @@ default values.
|
||||
| MF_EMAIL_FROM_ADDRESS | Email "from" address | |
|
||||
| MF_EMAIL_FROM_NAME | Email "from" name | |
|
||||
| MF_EMAIL_TEMPLATE | Email template for sending emails with password reset link | email.tmpl |
|
||||
| MF_TOKEN_SECRET | Password reset token signing secret | |
|
||||
| MF_TOKEN_DURATION | Token duration in minutes | 5 |
|
||||
| MF_TOKEN_RESET_ENDPOINT | Password request reset endpoint, for constructing link | /reset-request |
|
||||
|
||||
## Deployment
|
||||
@@ -72,8 +68,6 @@ services:
|
||||
MF_USERS_DB_SSL_KEY: [Path to the PEM encoded key file]
|
||||
MF_USERS_DB_SSL_ROOT_CERT: [Path to the PEM encoded root certificate file]
|
||||
MF_USERS_HTTP_PORT: [Service HTTP port]
|
||||
MF_USERS_GRPC_PORT: [Service gRPC port]
|
||||
MF_USERS_SECRET: [String used for signing tokens]
|
||||
MF_USERS_SERVER_CERT: [String path to server certificate in pem format]
|
||||
MF_USERS_SERVER_KEY: [String path to server key in pem format]
|
||||
MF_JAEGER_URL: [Jaeger server URL]
|
||||
@@ -85,8 +79,6 @@ services:
|
||||
MF_EMAIL_FROM_ADDRESS: [MF_EMAIL_FROM_ADDRESS]
|
||||
MF_EMAIL_FROM_NAME: [MF_EMAIL_FROM_NAME]
|
||||
MF_EMAIL_TEMPLATE: [MF_EMAIL_TEMPLATE]
|
||||
MF_TOKEN_SECRET: [MF_TOKEN_SECRET]
|
||||
MF_TOKEN_DURATION: [MF_TOKEN_DURATION]
|
||||
MF_TOKEN_RESET_ENDPOINT: [MF_TOKEN_RESET_ENDPOINT]
|
||||
```
|
||||
|
||||
@@ -105,7 +97,7 @@ make users
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
MF_USERS_LOG_LEVEL=[Users log level] MF_USERS_DB_HOST=[Database host address] MF_USERS_DB_PORT=[Database host port] MF_USERS_DB_USER=[Database user] MF_USERS_DB_PASS=[Database password] MF_USERS_DB=[Name of the database used by the service] MF_USERS_DB_SSL_MODE=[SSL mode to connect to the database with] MF_USERS_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_USERS_DB_SSL_KEY=[Path to the PEM encoded key file] MF_USERS_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_USERS_HTTP_PORT=[Service HTTP port] MF_USERS_GRPC_PORT=[Service gRPC port] MF_USERS_SECRET=[String used for signing tokens] MF_USERS_SERVER_CERT=[Path to server certificate] MF_USERS_SERVER_KEY=[Path to server key] MF_JAEGER_URL=[Jaeger server URL] MF_EMAIL_DRIVER=[Mail server driver smtp] MF_EMAIL_HOST=[Mail server host] MF_EMAIL_PORT=[Mail server port] MF_EMAIL_USERNAME=[Mail server username] MF_EMAIL_PASSWORD=[Mail server password] MF_EMAIL_FROM_ADDRESS=[Email from address] MF_EMAIL_FROM_NAME=[Email from name] MF_EMAIL_TEMPLATE=[Email template file] MF_TOKEN_SECRET=[Password reset token signing secret] MF_TOKEN_DURATION=[Password reset token duration] MF_TOKEN_RESET_ENDPOINT=[Password reset token endpoint] $GOBIN/mainflux-users
|
||||
MF_USERS_LOG_LEVEL=[Users log level] MF_USERS_DB_HOST=[Database host address] MF_USERS_DB_PORT=[Database host port] MF_USERS_DB_USER=[Database user] MF_USERS_DB_PASS=[Database password] MF_USERS_DB=[Name of the database used by the service] MF_USERS_DB_SSL_MODE=[SSL mode to connect to the database with] MF_USERS_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_USERS_DB_SSL_KEY=[Path to the PEM encoded key file] MF_USERS_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_USERS_HTTP_PORT=[Service HTTP port] MF_USERS_SERVER_CERT=[Path to server certificate] MF_USERS_SERVER_KEY=[Path to server key] MF_JAEGER_URL=[Jaeger server URL] MF_EMAIL_DRIVER=[Mail server driver smtp] MF_EMAIL_HOST=[Mail server host] MF_EMAIL_PORT=[Mail server port] MF_EMAIL_USERNAME=[Mail server username] MF_EMAIL_PASSWORD=[Mail server password] MF_EMAIL_FROM_ADDRESS=[Email from address] MF_EMAIL_FROM_NAME=[Email from name] MF_EMAIL_TEMPLATE=[Email template file] MF_TOKEN_RESET_ENDPOINT=[Password reset token endpoint] $GOBIN/mainflux-users
|
||||
```
|
||||
|
||||
If `MF_EMAIL_TEMPLATE` doesn't point to any file service will function but password reset functionality will not work.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http_test
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -15,12 +15,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
httpapi "github.com/mainflux/mainflux/users/api/http"
|
||||
"github.com/mainflux/mainflux/users/jwt"
|
||||
"github.com/mainflux/mainflux/users/api"
|
||||
"github.com/mainflux/mainflux/users/mocks"
|
||||
"github.com/mainflux/mainflux/users/token"
|
||||
"github.com/opentracing/opentracing-go/mocktracer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -62,16 +61,15 @@ func (tr testRequest) make() (*http.Response, error) {
|
||||
func newService() users.Service {
|
||||
repo := mocks.NewUserRepository()
|
||||
hasher := mocks.NewHasher()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
token := mocks.NewTokenizer()
|
||||
auth := mocks.NewAuthService(map[string]string{user.Email: user.Email})
|
||||
email := mocks.NewEmailer()
|
||||
|
||||
return users.New(repo, hasher, idp, email, token)
|
||||
return users.New(repo, hasher, auth, email)
|
||||
}
|
||||
|
||||
func newServer(svc users.Service) *httptest.Server {
|
||||
logger, _ := log.New(os.Stdout, log.Info.String())
|
||||
mux := httpapi.MakeHandler(svc, mocktracer.New(), logger)
|
||||
mux := api.MakeHandler(svc, mocktracer.New(), logger)
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
@@ -125,8 +123,9 @@ func TestLogin(t *testing.T) {
|
||||
ts := newServer(svc)
|
||||
defer ts.Close()
|
||||
client := ts.Client()
|
||||
j := jwt.New("secret")
|
||||
token, _ := j.TemporaryKey(user.Email)
|
||||
auth := mocks.NewAuthService(map[string]string{user.Email: user.Email})
|
||||
tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Issuer: user.Email, Type: 0})
|
||||
token := tkn.GetValue()
|
||||
tokenData := toJSON(map[string]string{"token": token})
|
||||
data := toJSON(user)
|
||||
invalidEmailData := toJSON(users.User{
|
||||
@@ -157,7 +156,7 @@ func TestLogin(t *testing.T) {
|
||||
{"login with invalid request format", "{", contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrMalformedEntity.Error()})},
|
||||
{"login with empty JSON request", "{}", contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrMalformedEntity.Error()})},
|
||||
{"login with empty request", "", contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrMalformedEntity.Error()})},
|
||||
{"login with missing content type", data, "", http.StatusUnsupportedMediaType, toJSON(errorRes{httpapi.ErrUnsupportedContentType.Error()})},
|
||||
{"login with missing content type", data, "", http.StatusUnsupportedMediaType, toJSON(errorRes{api.ErrUnsupportedContentType.Error()})},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -185,10 +184,10 @@ func TestUserInfo(t *testing.T) {
|
||||
defer ts.Close()
|
||||
client := ts.Client()
|
||||
svc.Register(context.Background(), user)
|
||||
j := jwt.New("secret")
|
||||
token, _ := j.TemporaryKey(user.Email)
|
||||
invalidToken, _ := j.TemporaryKey("non-exist@example.com")
|
||||
|
||||
auth := mocks.NewAuthService(map[string]string{user.Email: user.Email})
|
||||
tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Issuer: user.Email, Type: 0})
|
||||
token := tkn.GetValue()
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
@@ -196,7 +195,7 @@ func TestUserInfo(t *testing.T) {
|
||||
res string
|
||||
}{
|
||||
{"user info with valid token", token, http.StatusOK, ""},
|
||||
{"user info with invalid token", invalidToken, http.StatusForbidden, ""},
|
||||
{"user info with invalid token", "", http.StatusForbidden, ""},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -232,7 +231,7 @@ func TestPasswordResetRequest(t *testing.T) {
|
||||
expectedExisting := toJSON(struct {
|
||||
Msg string `json:"msg"`
|
||||
}{
|
||||
httpapi.MailSent,
|
||||
api.MailSent,
|
||||
})
|
||||
|
||||
svc.Register(context.Background(), user)
|
||||
@@ -246,10 +245,10 @@ func TestPasswordResetRequest(t *testing.T) {
|
||||
}{
|
||||
{"password reset request with valid email", data, contentType, http.StatusCreated, expectedExisting},
|
||||
{"password reset request with invalid email", nonexistentData, contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrUserNotFound.Error()})},
|
||||
{"password reset request with invalid request format", "{", contentType, http.StatusBadRequest, toJSON(errorRes{httpapi.ErrFailedDecode.Error()})},
|
||||
{"password reset request with invalid request format", "{", contentType, http.StatusBadRequest, toJSON(errorRes{api.ErrFailedDecode.Error()})},
|
||||
{"password reset request with empty JSON request", "{}", contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrMalformedEntity.Error()})},
|
||||
{"password reset request with empty request", "", contentType, http.StatusBadRequest, toJSON(errorRes{httpapi.ErrFailedDecode.Error()})},
|
||||
{"password reset request with missing content type", data, "", http.StatusUnsupportedMediaType, toJSON(errorRes{httpapi.ErrUnsupportedContentType.Error()})},
|
||||
{"password reset request with empty request", "", contentType, http.StatusBadRequest, toJSON(errorRes{api.ErrFailedDecode.Error()})},
|
||||
{"password reset request with missing content type", data, "", http.StatusUnsupportedMediaType, toJSON(errorRes{api.ErrUnsupportedContentType.Error()})},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -276,7 +275,6 @@ func TestPasswordReset(t *testing.T) {
|
||||
ts := newServer(svc)
|
||||
defer ts.Close()
|
||||
client := ts.Client()
|
||||
tokenizer := token.New([]byte("secret"), 1)
|
||||
resData := struct {
|
||||
Msg string `json:"msg"`
|
||||
}{
|
||||
@@ -293,17 +291,20 @@ func TestPasswordReset(t *testing.T) {
|
||||
resData.Msg = users.ErrUserNotFound.Error()
|
||||
|
||||
svc.Register(context.Background(), user)
|
||||
tok, _ := tokenizer.Generate(user.Email, 0)
|
||||
auth := mocks.NewAuthService(map[string]string{user.Email: user.Email})
|
||||
tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Issuer: user.Email, Type: 0})
|
||||
token := tkn.GetValue()
|
||||
|
||||
reqData.Password = user.Password
|
||||
reqData.ConfPass = user.Password
|
||||
reqData.Token = tok
|
||||
reqData.Token = token
|
||||
reqExisting := toJSON(reqData)
|
||||
|
||||
reqData.Token, _ = tokenizer.Generate("non-existentuser@example.com", 0)
|
||||
reqData.Token = "wrong"
|
||||
|
||||
reqNoExist := toJSON(reqData)
|
||||
reqData.Token, _ = tokenizer.Generate(user.Email, -5)
|
||||
|
||||
reqData.Token = token
|
||||
|
||||
reqData.ConfPass = "wrong"
|
||||
reqPassNoMatch := toJSON(reqData)
|
||||
@@ -316,13 +317,13 @@ func TestPasswordReset(t *testing.T) {
|
||||
res string
|
||||
tok string
|
||||
}{
|
||||
{"password reset with valid token", reqExisting, contentType, http.StatusCreated, expectedSuccess, tok},
|
||||
{"password reset with invalid token", reqNoExist, contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrUserNotFound.Error()}), tok},
|
||||
{"password reset with confirm password not matching", reqPassNoMatch, contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrMalformedEntity.Error()}), tok},
|
||||
{"password reset request with invalid request format", "{", contentType, http.StatusBadRequest, toJSON(errorRes{httpapi.ErrFailedDecode.Error()}), tok},
|
||||
{"password reset request with empty JSON request", "{}", contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrMalformedEntity.Error()}), tok},
|
||||
{"password reset request with empty request", "", contentType, http.StatusBadRequest, toJSON(errorRes{httpapi.ErrFailedDecode.Error()}), tok},
|
||||
{"password reset request with missing content type", reqExisting, "", http.StatusUnsupportedMediaType, toJSON(errorRes{httpapi.ErrUnsupportedContentType.Error()}), tok},
|
||||
{"password reset with valid token", reqExisting, contentType, http.StatusCreated, expectedSuccess, token},
|
||||
{"password reset with invalid token", reqNoExist, contentType, http.StatusForbidden, toJSON(errorRes{users.ErrUnauthorizedAccess.Error()}), token},
|
||||
{"password reset with confirm password not matching", reqPassNoMatch, contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrMalformedEntity.Error()}), token},
|
||||
{"password reset request with invalid request format", "{", contentType, http.StatusBadRequest, toJSON(errorRes{api.ErrFailedDecode.Error()}), token},
|
||||
{"password reset request with empty JSON request", "{}", contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrMalformedEntity.Error()}), token},
|
||||
{"password reset request with empty request", "", contentType, http.StatusBadRequest, toJSON(errorRes{api.ErrFailedDecode.Error()}), token},
|
||||
{"password reset request with missing content type", reqExisting, "", http.StatusUnsupportedMediaType, toJSON(errorRes{api.ErrUnsupportedContentType.Error()}), token},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -350,7 +351,10 @@ func TestPasswordChange(t *testing.T) {
|
||||
ts := newServer(svc)
|
||||
defer ts.Close()
|
||||
client := ts.Client()
|
||||
j := jwt.New("secret")
|
||||
auth := mocks.NewAuthService(map[string]string{user.Email: user.Email})
|
||||
|
||||
tkn, _ := auth.Issue(context.Background(), &mainflux.IssueReq{Issuer: user.Email, Type: 0})
|
||||
token := tkn.GetValue()
|
||||
resData := struct {
|
||||
Msg string `json:"msg"`
|
||||
}{
|
||||
@@ -366,18 +370,13 @@ func TestPasswordChange(t *testing.T) {
|
||||
resData.Msg = users.ErrUnauthorizedAccess.Error()
|
||||
|
||||
svc.Register(context.Background(), user)
|
||||
tok, _ := j.TemporaryKey(user.Email)
|
||||
tokNoUser, _ := j.TemporaryKey("non-existentuser@example.com")
|
||||
|
||||
reqData.Password = user.Password
|
||||
reqData.OldPassw = user.Password
|
||||
reqData.Token = tok
|
||||
reqData.Token = token
|
||||
dataResExisting := toJSON(reqData)
|
||||
|
||||
reqData.Token, _ = j.TemporaryKey(user.Email)
|
||||
|
||||
reqNoExist := toJSON(reqData)
|
||||
reqData.Token, _ = j.TemporaryKey(user.Email)
|
||||
|
||||
reqData.OldPassw = "wrong"
|
||||
reqWrongPass := toJSON(reqData)
|
||||
@@ -392,12 +391,12 @@ func TestPasswordChange(t *testing.T) {
|
||||
res string
|
||||
tok string
|
||||
}{
|
||||
{"password change with valid token", dataResExisting, contentType, http.StatusCreated, expectedSuccess, tok},
|
||||
{"password change with invalid token", reqNoExist, contentType, http.StatusForbidden, toJSON(errorRes{users.ErrUnauthorizedAccess.Error()}), tokNoUser},
|
||||
{"password change with invalid old password", reqWrongPass, contentType, http.StatusForbidden, toJSON(errorRes{users.ErrUnauthorizedAccess.Error()}), tok},
|
||||
{"password change with empty JSON request", "{}", contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrMalformedEntity.Error()}), tok},
|
||||
{"password change empty request", "", contentType, http.StatusBadRequest, toJSON(errorRes{httpapi.ErrFailedDecode.Error()}), tok},
|
||||
{"password change missing content type", dataResExisting, "", http.StatusUnsupportedMediaType, toJSON(errorRes{httpapi.ErrUnsupportedContentType.Error()}), tok},
|
||||
{"password change with valid token", dataResExisting, contentType, http.StatusCreated, expectedSuccess, token},
|
||||
{"password change with invalid token", reqNoExist, contentType, http.StatusForbidden, toJSON(errorRes{users.ErrUnauthorizedAccess.Error()}), ""},
|
||||
{"password change with invalid old password", reqWrongPass, contentType, http.StatusForbidden, toJSON(errorRes{users.ErrUnauthorizedAccess.Error()}), token},
|
||||
{"password change with empty JSON request", "{}", contentType, http.StatusBadRequest, toJSON(errorRes{users.ErrMalformedEntity.Error()}), token},
|
||||
{"password change empty request", "", contentType, http.StatusBadRequest, toJSON(errorRes{api.ErrFailedDecode.Error()}), token},
|
||||
{"password change missing content type", dataResExisting, "", http.StatusUnsupportedMediaType, toJSON(errorRes{api.ErrUnsupportedContentType.Error()}), token},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
kitot "github.com/go-kit/kit/tracing/opentracing"
|
||||
kitgrpc "github.com/go-kit/kit/transport/grpc"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var _ mainflux.UsersServiceClient = (*grpcClient)(nil)
|
||||
|
||||
type grpcClient struct {
|
||||
identify endpoint.Endpoint
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewClient returns new gRPC client instance.
|
||||
func NewClient(tracer opentracing.Tracer, conn *grpc.ClientConn, timeout time.Duration) mainflux.UsersServiceClient {
|
||||
endpoint := kitot.TraceClient(tracer, "identify")(kitgrpc.NewClient(
|
||||
conn,
|
||||
"mainflux.UsersService",
|
||||
"Identify",
|
||||
encodeIdentifyRequest,
|
||||
decodeIdentifyResponse,
|
||||
mainflux.UserID{},
|
||||
).Endpoint())
|
||||
|
||||
return &grpcClient{
|
||||
identify: endpoint,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (client grpcClient) Identify(ctx context.Context, token *mainflux.Token, _ ...grpc.CallOption) (*mainflux.UserID, error) {
|
||||
ctx, close := context.WithTimeout(ctx, client.timeout)
|
||||
defer close()
|
||||
|
||||
res, err := client.identify(ctx, identityReq{token.GetValue()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ir := res.(identityRes)
|
||||
return &mainflux.UserID{Value: ir.id}, ir.err
|
||||
}
|
||||
|
||||
func encodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(identityReq)
|
||||
return &mainflux.Token{Value: req.token}, nil
|
||||
}
|
||||
|
||||
func decodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
res := grpcRes.(*mainflux.UserID)
|
||||
return identityRes{res.GetValue(), nil}, nil
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
context "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func identifyEndpoint(svc users.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(identityReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := svc.Identify(req.token)
|
||||
if err != nil {
|
||||
return identityRes{}, err
|
||||
}
|
||||
return identityRes{id, nil}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
grpcapi "github.com/mainflux/mainflux/users/api/grpc"
|
||||
"github.com/mainflux/mainflux/users/jwt"
|
||||
"github.com/mainflux/mainflux/users/mocks"
|
||||
"github.com/opentracing/opentracing-go/mocktracer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const port = 8081
|
||||
|
||||
var (
|
||||
user = users.User{
|
||||
Email: "john.doe@email.com",
|
||||
Password: "pass",
|
||||
}
|
||||
svc users.Service
|
||||
)
|
||||
|
||||
func newService() users.Service {
|
||||
repo := mocks.NewUserRepository()
|
||||
hasher := mocks.NewHasher()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
token := mocks.NewTokenizer()
|
||||
email := mocks.NewEmailer()
|
||||
|
||||
return users.New(repo, hasher, idp, email, token)
|
||||
}
|
||||
|
||||
func startGRPCServer(svc users.Service, port int) {
|
||||
listener, _ := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
server := grpc.NewServer()
|
||||
mainflux.RegisterUsersServiceServer(server, grpcapi.NewServer(mocktracer.New(), svc))
|
||||
go server.Serve(listener)
|
||||
}
|
||||
|
||||
func TestIdentify(t *testing.T) {
|
||||
err := svc.Register(context.Background(), user)
|
||||
require.Nil(t, err, fmt.Sprintf("Error creating User: %s", err))
|
||||
|
||||
usersAddr := fmt.Sprintf("localhost:%d", port)
|
||||
conn, _ := grpc.Dial(usersAddr, grpc.WithInsecure())
|
||||
client := grpcapi.NewClient(mocktracer.New(), conn, time.Second)
|
||||
j := jwt.New("secret")
|
||||
token, _ := j.TemporaryKey(user.Email)
|
||||
|
||||
cases := map[string]struct {
|
||||
token string
|
||||
id string
|
||||
err error
|
||||
}{
|
||||
"identify user with valid token": {token, user.Email, nil},
|
||||
"identify user that doesn't exist": {"", "", status.Error(codes.InvalidArgument, "received invalid token request")},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
id, err := client.Identify(context.Background(), &mainflux.Token{Value: tc.token})
|
||||
assert.Equal(t, tc.id, id.GetValue(), fmt.Sprintf("%s: expected %s got %s", desc, tc.id, id.GetValue()))
|
||||
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc
|
||||
|
||||
import "github.com/mainflux/mainflux/users"
|
||||
|
||||
type identityReq struct {
|
||||
token string
|
||||
}
|
||||
|
||||
func (req identityReq) validate() error {
|
||||
if req.token == "" {
|
||||
return users.ErrMalformedEntity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
kitot "github.com/go-kit/kit/tracing/opentracing"
|
||||
kitgrpc "github.com/go-kit/kit/transport/grpc"
|
||||
mainflux "github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/errors"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var errGrpc = errors.New("GRPC error")
|
||||
var _ mainflux.UsersServiceServer = (*grpcServer)(nil)
|
||||
|
||||
type grpcServer struct {
|
||||
handler kitgrpc.Handler
|
||||
}
|
||||
|
||||
// NewServer returns new UsersServiceServer instance.
|
||||
func NewServer(tracer opentracing.Tracer, svc users.Service) mainflux.UsersServiceServer {
|
||||
handler := kitgrpc.NewServer(
|
||||
kitot.TraceServer(tracer, "identify")(identifyEndpoint(svc)),
|
||||
decodeIdentifyRequest,
|
||||
encodeIdentifyResponse,
|
||||
)
|
||||
return &grpcServer{handler}
|
||||
}
|
||||
|
||||
func (s *grpcServer) Identify(ctx context.Context, token *mainflux.Token) (*mainflux.UserID, error) {
|
||||
_, res, err := s.handler.ServeGRPC(ctx, token)
|
||||
if err != nil {
|
||||
return nil, encodeError(errors.Wrap(errGrpc, err))
|
||||
}
|
||||
return res.(*mainflux.UserID), nil
|
||||
}
|
||||
|
||||
func decodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(*mainflux.Token)
|
||||
return identityReq{req.GetValue()}, nil
|
||||
}
|
||||
|
||||
func encodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
|
||||
res := grpcRes.(identityRes)
|
||||
if res.err != nil {
|
||||
return &mainflux.UserID{Value: res.id}, encodeError(errors.Wrap(errGrpc, res.err))
|
||||
}
|
||||
return &mainflux.UserID{Value: res.id}, nil
|
||||
|
||||
}
|
||||
|
||||
func encodeError(err errors.Error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case errors.Contains(err, users.ErrMalformedEntity):
|
||||
return status.Error(codes.InvalidArgument, "received invalid token request")
|
||||
case errors.Contains(err, users.ErrUnauthorizedAccess):
|
||||
return status.Error(codes.Unauthenticated, "failed to identify user from token")
|
||||
default:
|
||||
return status.Error(codes.Internal, "internal server error")
|
||||
}
|
||||
}
|
||||
@@ -52,19 +52,6 @@ func (lm *loggingMiddleware) Login(ctx context.Context, user users.User) (token
|
||||
return lm.svc.Login(ctx, user)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Identify(key string) (id string, err errors.Error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method identity for user %s took %s to complete", id, time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Identify(key)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) UserInfo(ctx context.Context, token string) (u users.User, err errors.Error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method user_info for user %s took %s to complete", u.Email, time.Since(begin))
|
||||
|
||||
@@ -48,15 +48,6 @@ func (ms *metricsMiddleware) Login(ctx context.Context, user users.User) (string
|
||||
return ms.svc.Login(ctx, user)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) Identify(key string) (string, errors.Error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "identity").Add(1)
|
||||
ms.latency.With("method", "identity").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.Identify(key)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) UserInfo(ctx context.Context, token string) (users.User, errors.Error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "user_info").Add(1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/mainflux/mainflux/errors"
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package http
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package bcrypt provides a hasher implementation utilising bcrypt.
|
||||
// Package bcrypt provides a hasher implementation utilizing bcrypt.
|
||||
package bcrypt
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package users
|
||||
|
||||
import "github.com/mainflux/mainflux/errors"
|
||||
|
||||
// IdentityProvider specifies an API for identity management via security
|
||||
// tokens.
|
||||
type IdentityProvider interface {
|
||||
// TemporaryKey generates the temporary access token.
|
||||
TemporaryKey(string) (string, errors.Error)
|
||||
|
||||
// Identity extracts the entity identifier given its secret key.
|
||||
Identity(string) (string, errors.Error)
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package jwt provides a JWT identity provider.
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/mainflux/mainflux/errors"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
)
|
||||
|
||||
const (
|
||||
issuer string = "mainflux"
|
||||
duration time.Duration = 10 * time.Hour
|
||||
)
|
||||
|
||||
var _ users.IdentityProvider = (*jwtIdentityProvider)(nil)
|
||||
|
||||
type jwtIdentityProvider struct {
|
||||
secret string
|
||||
}
|
||||
|
||||
// New instantiates a JWT identity provider.
|
||||
func New(secret string) users.IdentityProvider {
|
||||
return &jwtIdentityProvider{secret}
|
||||
}
|
||||
|
||||
func (idp *jwtIdentityProvider) TemporaryKey(id string) (string, errors.Error) {
|
||||
now := time.Now().UTC()
|
||||
exp := now.Add(duration)
|
||||
|
||||
claims := jwt.StandardClaims{
|
||||
Subject: id,
|
||||
Issuer: issuer,
|
||||
IssuedAt: now.Unix(),
|
||||
ExpiresAt: exp.Unix(),
|
||||
}
|
||||
|
||||
return idp.jwt(claims)
|
||||
}
|
||||
|
||||
func (idp *jwtIdentityProvider) Identity(key string) (string, errors.Error) {
|
||||
token, err := jwt.Parse(key, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, users.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
return []byte(idp.secret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", errors.Wrap(users.ErrUnauthorizedAccess, err)
|
||||
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
if sub := claims["sub"]; sub != nil {
|
||||
return sub.(string), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", users.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
func (idp *jwtIdentityProvider) jwt(claims jwt.StandardClaims) (string, errors.Error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tok, err := token.SignedString([]byte(idp.secret))
|
||||
if err != nil {
|
||||
return tok, errors.Wrap(users.ErrGetToken, err)
|
||||
}
|
||||
return tok, nil
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var _ mainflux.AuthNServiceClient = (*authNServiceMock)(nil)
|
||||
|
||||
type authNServiceMock struct {
|
||||
users map[string]string
|
||||
}
|
||||
|
||||
// NewAuthService creates mock of users service.
|
||||
func NewAuthService(users map[string]string) mainflux.AuthNServiceClient {
|
||||
return &authNServiceMock{users}
|
||||
}
|
||||
|
||||
func (svc authNServiceMock) Identify(ctx context.Context, in *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) {
|
||||
if id, ok := svc.users[in.Value]; ok {
|
||||
return &mainflux.UserID{Value: id}, nil
|
||||
}
|
||||
return nil, users.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
func (svc authNServiceMock) Issue(ctx context.Context, in *mainflux.IssueReq, opts ...grpc.CallOption) (*mainflux.Token, error) {
|
||||
if id, ok := svc.users[in.GetIssuer()]; ok {
|
||||
switch in.Type {
|
||||
default:
|
||||
return &mainflux.Token{Value: id}, nil
|
||||
}
|
||||
}
|
||||
return nil, users.ErrUnauthorizedAccess
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"github.com/mainflux/mainflux/users"
|
||||
"github.com/mainflux/mainflux/users/jwt"
|
||||
)
|
||||
|
||||
// NewIdentityProvider creates "mirror" identity provider, i.e. generated
|
||||
// token will hold value provided by the caller.
|
||||
func NewIdentityProvider() users.IdentityProvider {
|
||||
return jwt.New("secret")
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"github.com/mainflux/mainflux/users"
|
||||
"github.com/mainflux/mainflux/users/token"
|
||||
)
|
||||
|
||||
// NewTokenizer provides tokenizer for the test
|
||||
func NewTokenizer() users.Tokenizer {
|
||||
return token.New([]byte("secret"), 1)
|
||||
}
|
||||
+37
-29
@@ -6,6 +6,8 @@ package users
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/authn"
|
||||
"github.com/mainflux/mainflux/errors"
|
||||
)
|
||||
|
||||
@@ -58,11 +60,6 @@ type Service interface {
|
||||
// identified by the non-nil error values in the response.
|
||||
Login(context.Context, User) (string, errors.Error)
|
||||
|
||||
// Identify validates user's token. If token is valid, user's id
|
||||
// is returned. If token is invalid, or invocation failed for some
|
||||
// other reason, non-nil error values are returned in response.
|
||||
Identify(string) (string, errors.Error)
|
||||
|
||||
// Get authenticated user info for the given token
|
||||
UserInfo(ctx context.Context, token string) (User, errors.Error)
|
||||
|
||||
@@ -89,14 +86,18 @@ var _ Service = (*usersService)(nil)
|
||||
type usersService struct {
|
||||
users UserRepository
|
||||
hasher Hasher
|
||||
idp IdentityProvider
|
||||
token Tokenizer
|
||||
email Emailer
|
||||
auth mainflux.AuthNServiceClient
|
||||
}
|
||||
|
||||
// New instantiates the users service implementation
|
||||
func New(users UserRepository, hasher Hasher, idp IdentityProvider, m Emailer, t Tokenizer) Service {
|
||||
return &usersService{users: users, hasher: hasher, idp: idp, email: m, token: t}
|
||||
func New(users UserRepository, hasher Hasher, auth mainflux.AuthNServiceClient, m Emailer) Service {
|
||||
return &usersService{
|
||||
users: users,
|
||||
hasher: hasher,
|
||||
auth: auth,
|
||||
email: m,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc usersService) Register(ctx context.Context, user User) errors.Error {
|
||||
@@ -119,21 +120,13 @@ func (svc usersService) Login(ctx context.Context, user User) (string, errors.Er
|
||||
return "", errors.Wrap(ErrUnauthorizedAccess, err)
|
||||
}
|
||||
|
||||
return svc.idp.TemporaryKey(user.Email)
|
||||
}
|
||||
|
||||
func (svc usersService) Identify(token string) (string, errors.Error) {
|
||||
id, err := svc.idp.Identity(token)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(ErrUnauthorizedAccess, err)
|
||||
}
|
||||
return id, nil
|
||||
return svc.issue(ctx, dbUser.Email, authn.UserKey)
|
||||
}
|
||||
|
||||
func (svc usersService) UserInfo(ctx context.Context, token string) (User, errors.Error) {
|
||||
id, err := svc.idp.Identity(token)
|
||||
id, err := svc.identify(ctx, token)
|
||||
if err != nil {
|
||||
return User{}, errors.Wrap(ErrUnauthorizedAccess, err)
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
dbUser, err := svc.users.RetrieveByID(ctx, id)
|
||||
@@ -146,13 +139,12 @@ func (svc usersService) UserInfo(ctx context.Context, token string) (User, error
|
||||
Password: "",
|
||||
Metadata: dbUser.Metadata,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (svc usersService) UpdateUser(ctx context.Context, token string, u User) errors.Error {
|
||||
email, err := svc.idp.Identity(token)
|
||||
email, err := svc.identify(ctx, token)
|
||||
if err != nil {
|
||||
return ErrUnauthorizedAccess
|
||||
return errors.Wrap(ErrUnauthorizedAccess, err)
|
||||
}
|
||||
|
||||
user := User{
|
||||
@@ -169,17 +161,17 @@ func (svc usersService) GenerateResetToken(ctx context.Context, email, host stri
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
tok, err := svc.token.Generate(email, 0)
|
||||
t, err := svc.issue(ctx, email, authn.RecoveryKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(ErrGeneratingResetToken, err)
|
||||
}
|
||||
return svc.SendPasswordReset(ctx, host, email, tok)
|
||||
return svc.SendPasswordReset(ctx, host, email, t)
|
||||
}
|
||||
|
||||
func (svc usersService) ResetPassword(ctx context.Context, resetToken, password string) errors.Error {
|
||||
email, err := svc.token.Verify(resetToken)
|
||||
email, err := svc.identify(ctx, resetToken)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(ErrUnauthorizedAccess, err)
|
||||
}
|
||||
|
||||
u, err := svc.users.RetrieveByID(ctx, email)
|
||||
@@ -195,7 +187,7 @@ func (svc usersService) ResetPassword(ctx context.Context, resetToken, password
|
||||
}
|
||||
|
||||
func (svc usersService) ChangePassword(ctx context.Context, authToken, password, oldPassword string) errors.Error {
|
||||
email, err := svc.idp.Identity(authToken)
|
||||
email, err := svc.identify(ctx, authToken)
|
||||
if err != nil {
|
||||
return errors.Wrap(ErrUnauthorizedAccess, err)
|
||||
}
|
||||
@@ -204,7 +196,7 @@ func (svc usersService) ChangePassword(ctx context.Context, authToken, password,
|
||||
Email: email,
|
||||
Password: oldPassword,
|
||||
}
|
||||
if _, err = svc.Login(ctx, u); err != nil {
|
||||
if _, err := svc.Login(ctx, u); err != nil {
|
||||
return ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
@@ -225,3 +217,19 @@ func (svc usersService) SendPasswordReset(_ context.Context, host, email, token
|
||||
to := []string{email}
|
||||
return svc.email.SendPasswordReset(to, host, token)
|
||||
}
|
||||
|
||||
func (svc usersService) identify(ctx context.Context, token string) (string, errors.Error) {
|
||||
email, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
|
||||
if err != nil {
|
||||
return "", errors.Wrap(ErrUnauthorizedAccess, err)
|
||||
}
|
||||
return email.GetValue(), nil
|
||||
}
|
||||
|
||||
func (svc usersService) issue(ctx context.Context, email string, keyType uint32) (string, errors.Error) {
|
||||
key, err := svc.auth.Issue(ctx, &mainflux.IssueReq{Issuer: email, Type: keyType})
|
||||
if err != nil {
|
||||
return "", errors.Wrap(ErrUserNotFound, err)
|
||||
}
|
||||
return key.GetValue(), nil
|
||||
}
|
||||
|
||||
+18
-29
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/errors"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
|
||||
@@ -26,11 +27,10 @@ var (
|
||||
func newService() users.Service {
|
||||
repo := mocks.NewUserRepository()
|
||||
hasher := mocks.NewHasher()
|
||||
idp := mocks.NewIdentityProvider()
|
||||
token := mocks.NewTokenizer()
|
||||
auth := mocks.NewAuthService(map[string]string{user.Email: user.Email})
|
||||
e := mocks.NewEmailer()
|
||||
|
||||
return users.New(repo, hasher, idp, e, token)
|
||||
return users.New(repo, hasher, auth, e)
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
@@ -70,6 +70,12 @@ func TestRegister(t *testing.T) {
|
||||
func TestLogin(t *testing.T) {
|
||||
svc := newService()
|
||||
svc.Register(context.Background(), user)
|
||||
noAuthUser := users.User{
|
||||
Email: "email@test.com",
|
||||
Password: "pwd",
|
||||
}
|
||||
svc.Register(context.Background(), user)
|
||||
svc.Register(context.Background(), noAuthUser)
|
||||
|
||||
cases := map[string]struct {
|
||||
user users.User
|
||||
@@ -93,6 +99,10 @@ func TestLogin(t *testing.T) {
|
||||
},
|
||||
err: users.ErrUnauthorizedAccess,
|
||||
},
|
||||
"login failed auth": {
|
||||
user: noAuthUser,
|
||||
err: users.ErrUnauthorizedAccess,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
@@ -101,25 +111,6 @@ func TestLogin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentify(t *testing.T) {
|
||||
svc := newService()
|
||||
svc.Register(context.Background(), user)
|
||||
key, _ := svc.Login(context.Background(), user)
|
||||
|
||||
cases := map[string]struct {
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"valid token's identity": {key, nil},
|
||||
"invalid token's identity": {"", users.ErrUnauthorizedAccess},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
_, err := svc.Identify(tc.key)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserInfo(t *testing.T) {
|
||||
svc := newService()
|
||||
svc.Register(context.Background(), user)
|
||||
@@ -209,23 +200,21 @@ func TestChangePassword(t *testing.T) {
|
||||
func TestResetPassword(t *testing.T) {
|
||||
svc := newService()
|
||||
svc.Register(context.Background(), user)
|
||||
tokenizer := mocks.NewTokenizer()
|
||||
resetToken, _ := tokenizer.Generate(user.Email, 0)
|
||||
resetNonExisting, _ := tokenizer.Generate(nonExistingUser.Email, 0)
|
||||
|
||||
auth := mocks.NewAuthService(map[string]string{user.Email: user.Email})
|
||||
resetToken, err := auth.Issue(context.Background(), &mainflux.IssueReq{Issuer: user.Email, Type: 2})
|
||||
assert.Nil(t, err, fmt.Sprintf("Generating reset token expected to succeed: %s", err))
|
||||
cases := map[string]struct {
|
||||
token string
|
||||
password string
|
||||
err error
|
||||
}{
|
||||
"valid user reset password ": {resetToken, "newpassword", nil},
|
||||
"invalid user reset password ": {resetNonExisting, "newpassword", users.ErrUserNotFound},
|
||||
"valid user reset password ": {resetToken.GetValue(), user.Email, nil},
|
||||
"invalid user reset password ": {"", "newpassword", users.ErrUnauthorizedAccess},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
err := svc.ResetPassword(context.Background(), tc.token, tc.password)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package token provides password recovery token generation with jwt
|
||||
// Token is sent by email to user as part of recovery URL
|
||||
// Token is signed by secret signature
|
||||
package token
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/mainflux/mainflux/errors"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
)
|
||||
|
||||
var (
|
||||
// errExpiredToken password reset token has expired
|
||||
errExpiredToken = errors.New("Token is expired")
|
||||
// errWrongSignature wrong signature
|
||||
errWrongSignature = errors.New("Wrong token signature")
|
||||
// errValidateToken represents error when validating token
|
||||
errValidateToken = errors.New("Validate token failed")
|
||||
)
|
||||
|
||||
type tokenizer struct {
|
||||
hmacSampleSecret []byte // secret for signing token
|
||||
tokenDuration int // token in duration in min
|
||||
}
|
||||
|
||||
// New creation of tokenizer.
|
||||
func New(hmacSampleSecret []byte, tokenDuration int) users.Tokenizer {
|
||||
return &tokenizer{hmacSampleSecret: hmacSampleSecret, tokenDuration: tokenDuration}
|
||||
}
|
||||
|
||||
func (t *tokenizer) Generate(email string, offset int) (string, errors.Error) {
|
||||
exp := t.tokenDuration + offset
|
||||
if exp < 0 {
|
||||
exp = 0
|
||||
}
|
||||
expires := time.Now().Add(time.Minute * time.Duration(exp))
|
||||
nbf := time.Now()
|
||||
|
||||
// Create a new token object, specifying signing method and the claims
|
||||
// you would like it to contain
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"email": email,
|
||||
"exp": expires.Unix(),
|
||||
"nbf": nbf.Unix(),
|
||||
})
|
||||
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
tokenString, err := token.SignedString(t.hmacSampleSecret)
|
||||
if err != nil {
|
||||
return tokenString, errors.Wrap(users.ErrGetToken, err)
|
||||
}
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
// Verify verifies token validity
|
||||
func (t *tokenizer) Verify(tok string) (string, errors.Error) {
|
||||
email := ""
|
||||
token, err := jwt.Parse(tok, func(token *jwt.Token) (interface{}, error) {
|
||||
// Don't forget to validate the alg is what you expect:
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errWrongSignature
|
||||
}
|
||||
|
||||
// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
|
||||
return t.hmacSampleSecret, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return email, errors.Wrap(errValidateToken, err)
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
if claims.VerifyExpiresAt(time.Now().Unix(), false) == false {
|
||||
return "", errExpiredToken
|
||||
}
|
||||
email = claims["email"].(string)
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) Mainflux
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package token_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mainflux/mainflux/users/token"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var email = "johnsnow@gmai.com"
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
_, err := token.New([]byte("secret"), 1).Generate(email, 0)
|
||||
assert.Nil(t, err, fmt.Sprintf("Testing token generate: %s.\n", err))
|
||||
}
|
||||
|
||||
func TestVerify(t *testing.T) {
|
||||
tok, err := token.New([]byte("secret"), 1).Generate(email, 0)
|
||||
assert.Nil(t, err, fmt.Sprintf("Token generation failed: %s.\n", err))
|
||||
|
||||
_, err = token.New([]byte("secret"), 1).Verify(tok)
|
||||
assert.Nil(t, err, fmt.Sprintf("Token verification failed: %s.\n", err))
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user