* Updated Users Service

* user-old => test/users
This commit is contained in:
ben-toogood
2021-01-22 13:39:34 +00:00
committed by GitHub
parent e6495ff6d7
commit 055517ec14
22 changed files with 3359 additions and 1169 deletions

3
test/users/Dockerfile Normal file
View File

@@ -0,0 +1,3 @@
FROM alpine:3.2
ADD users /users
ENTRYPOINT [ "/users" ]

22
test/users/Makefile Normal file
View File

@@ -0,0 +1,22 @@
GOPATH:=$(shell go env GOPATH)
.PHONY: init
init:
go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
go get github.com/micro/micro/v3/cmd/protoc-gen-micro
.PHONY: proto
proto:
protoc --proto_path=. --micro_out=. --go_out=:. proto/users.proto
.PHONY: build
build:
go build -o users *.go
.PHONY: test
test:
go test -v ./... -cover
.PHONY: docker
docker:
docker build . -t users:latest

73
test/users/README.md Normal file
View File

@@ -0,0 +1,73 @@
# Users Service
A user service for storing accounts and simple auth.
## Getting started
```
micro run github.com/micro/services/users
```
## Usage
User server implements the following RPC Methods
Users
- Create
- Read
- Update
- Delete
- Search
- UpdatePassword
- Login
- Logout
- ReadSession
### Create
```shell
micro call users Users.Create '{"id": "ff3c06de-9e43-41c7-9bab-578f6b4ad32b", "username": "asim", "email": "asim@example.com", "password": "password1"}'
```
### Read
```shell
micro call users Users.Read '{"id": "ff3c06de-9e43-41c7-9bab-578f6b4ad32b"}'
```
### Update
```shell
micro call users Users.Update '{"id": "ff3c06de-9e43-41c7-9bab-578f6b4ad32b", "username": "asim", "email": "asim+update@example.com"}'
```
### Update Password
```shell
micro call users Users.UpdatePassword '{"userId": "ff3c06de-9e43-41c7-9bab-578f6b4ad32b", "oldPassword": "password1", "newPassword": "newpassword1", "confirmPassword": "newpassword1" }'
```
### Delete
```shell
micro call users Users.Delete '{"id": "ff3c06de-9e43-41c7-9bab-578f6b4ad32b"}'
```
### Login
```shell
micro call users Users.Login '{"username": "asim", "password": "password1"}'
```
### Read Session
```shell
micro call users Users.ReadSession '{"sessionId": "sr7UEBmIMg5hYOgiljnhrd4XLsnalNewBV9KzpZ9aD8w37b3jRmEujGtKGcGlXPg1yYoSHR3RLy66ugglw0tofTNGm57NrNYUHsFxfwuGC6pvCn8BecB7aEF6UxTyVFq"}'
```
### Logout
```shell
micro call users Users.Logout '{"sessionId": "sr7UEBmIMg5hYOgiljnhrd4XLsnalNewBV9KzpZ9aD8w37b3jRmEujGtKGcGlXPg1yYoSHR3RLy66ugglw0tofTNGm57NrNYUHsFxfwuGC6pvCn8BecB7aEF6UxTyVFq"}'
```

View File

@@ -6,7 +6,7 @@ import (
"github.com/micro/dev/model"
"github.com/micro/micro/v3/service/store"
user "github.com/micro/services/users/proto"
user "github.com/micro/services/test/users/proto"
)
type pw struct {

3
test/users/generate.go Normal file
View File

@@ -0,0 +1,3 @@
package main
//go:generate make proto

View File

@@ -0,0 +1,174 @@
package handler
import (
"crypto/rand"
"encoding/base64"
"strings"
"time"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/services/test/users/domain"
pb "github.com/micro/services/test/users/proto"
"golang.org/x/crypto/bcrypt"
"golang.org/x/net/context"
)
const (
x = "cruft123"
)
var (
alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
)
func random(i int) string {
bytes := make([]byte, i)
for {
rand.Read(bytes)
for i, b := range bytes {
bytes[i] = alphanum[b%byte(len(alphanum))]
}
return string(bytes)
}
return "ughwhy?!!!"
}
type Users struct {
domain *domain.Domain
}
func NewUsers() *Users {
return &Users{
domain: domain.New(),
}
}
func (s *Users) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
if len(req.Password) < 8 {
return errors.InternalServerError("users.Create.Check", "Password is less than 8 characters")
}
salt := random(16)
h, err := bcrypt.GenerateFromPassword([]byte(x+salt+req.Password), 10)
if err != nil {
return errors.InternalServerError("users.Create", err.Error())
}
pp := base64.StdEncoding.EncodeToString(h)
return s.domain.Create(&pb.User{
Id: req.Id,
Username: strings.ToLower(req.Username),
Email: strings.ToLower(req.Email),
}, salt, pp)
}
func (s *Users) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
user, err := s.domain.Read(req.Id)
if err != nil {
return err
}
rsp.User = user
return nil
}
func (s *Users) Update(ctx context.Context, req *pb.UpdateRequest, rsp *pb.UpdateResponse) error {
return s.domain.Update(&pb.User{
Id: req.Id,
Username: strings.ToLower(req.Username),
Email: strings.ToLower(req.Email),
})
}
func (s *Users) Delete(ctx context.Context, req *pb.DeleteRequest, rsp *pb.DeleteResponse) error {
return s.domain.Delete(req.Id)
}
func (s *Users) Search(ctx context.Context, req *pb.SearchRequest, rsp *pb.SearchResponse) error {
users, err := s.domain.Search(req.Username, req.Email, req.Limit, req.Offset)
if err != nil {
return err
}
rsp.Users = users
return nil
}
func (s *Users) UpdatePassword(ctx context.Context, req *pb.UpdatePasswordRequest, rsp *pb.UpdatePasswordResponse) error {
usr, err := s.domain.Read(req.UserId)
if err != nil {
return errors.InternalServerError("users.updatepassword", err.Error())
}
if req.NewPassword != req.ConfirmPassword {
return errors.InternalServerError("users.updatepassword", "Passwords don't math")
}
salt, hashed, err := s.domain.SaltAndPassword(usr.Username, usr.Email)
if err != nil {
return errors.InternalServerError("users.updatepassword", err.Error())
}
hh, err := base64.StdEncoding.DecodeString(hashed)
if err != nil {
return errors.InternalServerError("users.updatepassword", err.Error())
}
if err := bcrypt.CompareHashAndPassword(hh, []byte(x+salt+req.OldPassword)); err != nil {
return errors.Unauthorized("users.updatepassword", err.Error())
}
salt = random(16)
h, err := bcrypt.GenerateFromPassword([]byte(x+salt+req.NewPassword), 10)
if err != nil {
return errors.InternalServerError("users.updatepassword", err.Error())
}
pp := base64.StdEncoding.EncodeToString(h)
if err := s.domain.UpdatePassword(req.UserId, salt, pp); err != nil {
return errors.InternalServerError("users.updatepassword", err.Error())
}
return nil
}
func (s *Users) Login(ctx context.Context, req *pb.LoginRequest, rsp *pb.LoginResponse) error {
username := strings.ToLower(req.Username)
email := strings.ToLower(req.Email)
salt, hashed, err := s.domain.SaltAndPassword(username, email)
if err != nil {
return err
}
hh, err := base64.StdEncoding.DecodeString(hashed)
if err != nil {
return errors.InternalServerError("users.Login", err.Error())
}
if err := bcrypt.CompareHashAndPassword(hh, []byte(x+salt+req.Password)); err != nil {
return errors.Unauthorized("users.login", err.Error())
}
// save session
sess := &pb.Session{
Id: random(128),
Username: username,
Email: email,
Created: time.Now().Unix(),
Expires: time.Now().Add(time.Hour * 24 * 7).Unix(),
}
if err := s.domain.CreateSession(sess); err != nil {
return errors.InternalServerError("users.Login", err.Error())
}
rsp.Session = sess
return nil
}
func (s *Users) Logout(ctx context.Context, req *pb.LogoutRequest, rsp *pb.LogoutResponse) error {
return s.domain.DeleteSession(req.SessionId)
}
func (s *Users) ReadSession(ctx context.Context, req *pb.ReadSessionRequest, rsp *pb.ReadSessionResponse) error {
sess, err := s.domain.ReadSession(req.SessionId)
if err != nil {
return err
}
rsp.Session = sess
return nil
}

22
test/users/main.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import (
"github.com/micro/micro/v3/service"
"github.com/micro/micro/v3/service/logger"
"github.com/micro/services/test/users/handler"
proto "github.com/micro/services/test/users/proto"
)
func main() {
service := service.New(
service.Name("users"),
)
service.Init()
proto.RegisterUsersHandler(service.Server(), handler.NewUsers())
if err := service.Run(); err != nil {
logger.Fatal(err)
}
}

1
test/users/micro.mu Normal file
View File

@@ -0,0 +1 @@
service users

View File

@@ -0,0 +1,998 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: proto/users.proto
package users
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type User struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
Created int64 `protobuf:"varint,4,opt,name=created,proto3" json:"created,omitempty"`
Updated int64 `protobuf:"varint,5,opt,name=updated,proto3" json:"updated,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *User) Reset() { *m = User{} }
func (m *User) String() string { return proto.CompactTextString(m) }
func (*User) ProtoMessage() {}
func (*User) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{0}
}
func (m *User) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_User.Unmarshal(m, b)
}
func (m *User) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_User.Marshal(b, m, deterministic)
}
func (m *User) XXX_Merge(src proto.Message) {
xxx_messageInfo_User.Merge(m, src)
}
func (m *User) XXX_Size() int {
return xxx_messageInfo_User.Size(m)
}
func (m *User) XXX_DiscardUnknown() {
xxx_messageInfo_User.DiscardUnknown(m)
}
var xxx_messageInfo_User proto.InternalMessageInfo
func (m *User) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *User) GetUsername() string {
if m != nil {
return m.Username
}
return ""
}
func (m *User) GetEmail() string {
if m != nil {
return m.Email
}
return ""
}
func (m *User) GetCreated() int64 {
if m != nil {
return m.Created
}
return 0
}
func (m *User) GetUpdated() int64 {
if m != nil {
return m.Updated
}
return 0
}
type Session struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
Created int64 `protobuf:"varint,4,opt,name=created,proto3" json:"created,omitempty"`
Expires int64 `protobuf:"varint,5,opt,name=expires,proto3" json:"expires,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Session) Reset() { *m = Session{} }
func (m *Session) String() string { return proto.CompactTextString(m) }
func (*Session) ProtoMessage() {}
func (*Session) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{1}
}
func (m *Session) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Session.Unmarshal(m, b)
}
func (m *Session) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Session.Marshal(b, m, deterministic)
}
func (m *Session) XXX_Merge(src proto.Message) {
xxx_messageInfo_Session.Merge(m, src)
}
func (m *Session) XXX_Size() int {
return xxx_messageInfo_Session.Size(m)
}
func (m *Session) XXX_DiscardUnknown() {
xxx_messageInfo_Session.DiscardUnknown(m)
}
var xxx_messageInfo_Session proto.InternalMessageInfo
func (m *Session) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *Session) GetUsername() string {
if m != nil {
return m.Username
}
return ""
}
func (m *Session) GetEmail() string {
if m != nil {
return m.Email
}
return ""
}
func (m *Session) GetCreated() int64 {
if m != nil {
return m.Created
}
return 0
}
func (m *Session) GetExpires() int64 {
if m != nil {
return m.Expires
}
return 0
}
type CreateRequest struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CreateRequest) Reset() { *m = CreateRequest{} }
func (m *CreateRequest) String() string { return proto.CompactTextString(m) }
func (*CreateRequest) ProtoMessage() {}
func (*CreateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{2}
}
func (m *CreateRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CreateRequest.Unmarshal(m, b)
}
func (m *CreateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CreateRequest.Marshal(b, m, deterministic)
}
func (m *CreateRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_CreateRequest.Merge(m, src)
}
func (m *CreateRequest) XXX_Size() int {
return xxx_messageInfo_CreateRequest.Size(m)
}
func (m *CreateRequest) XXX_DiscardUnknown() {
xxx_messageInfo_CreateRequest.DiscardUnknown(m)
}
var xxx_messageInfo_CreateRequest proto.InternalMessageInfo
func (m *CreateRequest) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *CreateRequest) GetUsername() string {
if m != nil {
return m.Username
}
return ""
}
func (m *CreateRequest) GetEmail() string {
if m != nil {
return m.Email
}
return ""
}
func (m *CreateRequest) GetPassword() string {
if m != nil {
return m.Password
}
return ""
}
type CreateResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CreateResponse) Reset() { *m = CreateResponse{} }
func (m *CreateResponse) String() string { return proto.CompactTextString(m) }
func (*CreateResponse) ProtoMessage() {}
func (*CreateResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{3}
}
func (m *CreateResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CreateResponse.Unmarshal(m, b)
}
func (m *CreateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CreateResponse.Marshal(b, m, deterministic)
}
func (m *CreateResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_CreateResponse.Merge(m, src)
}
func (m *CreateResponse) XXX_Size() int {
return xxx_messageInfo_CreateResponse.Size(m)
}
func (m *CreateResponse) XXX_DiscardUnknown() {
xxx_messageInfo_CreateResponse.DiscardUnknown(m)
}
var xxx_messageInfo_CreateResponse proto.InternalMessageInfo
type DeleteRequest struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *DeleteRequest) Reset() { *m = DeleteRequest{} }
func (m *DeleteRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteRequest) ProtoMessage() {}
func (*DeleteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{4}
}
func (m *DeleteRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DeleteRequest.Unmarshal(m, b)
}
func (m *DeleteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_DeleteRequest.Marshal(b, m, deterministic)
}
func (m *DeleteRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_DeleteRequest.Merge(m, src)
}
func (m *DeleteRequest) XXX_Size() int {
return xxx_messageInfo_DeleteRequest.Size(m)
}
func (m *DeleteRequest) XXX_DiscardUnknown() {
xxx_messageInfo_DeleteRequest.DiscardUnknown(m)
}
var xxx_messageInfo_DeleteRequest proto.InternalMessageInfo
func (m *DeleteRequest) GetId() string {
if m != nil {
return m.Id
}
return ""
}
type DeleteResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *DeleteResponse) Reset() { *m = DeleteResponse{} }
func (m *DeleteResponse) String() string { return proto.CompactTextString(m) }
func (*DeleteResponse) ProtoMessage() {}
func (*DeleteResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{5}
}
func (m *DeleteResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DeleteResponse.Unmarshal(m, b)
}
func (m *DeleteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_DeleteResponse.Marshal(b, m, deterministic)
}
func (m *DeleteResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_DeleteResponse.Merge(m, src)
}
func (m *DeleteResponse) XXX_Size() int {
return xxx_messageInfo_DeleteResponse.Size(m)
}
func (m *DeleteResponse) XXX_DiscardUnknown() {
xxx_messageInfo_DeleteResponse.DiscardUnknown(m)
}
var xxx_messageInfo_DeleteResponse proto.InternalMessageInfo
type ReadRequest struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ReadRequest) Reset() { *m = ReadRequest{} }
func (m *ReadRequest) String() string { return proto.CompactTextString(m) }
func (*ReadRequest) ProtoMessage() {}
func (*ReadRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{6}
}
func (m *ReadRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadRequest.Unmarshal(m, b)
}
func (m *ReadRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ReadRequest.Marshal(b, m, deterministic)
}
func (m *ReadRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ReadRequest.Merge(m, src)
}
func (m *ReadRequest) XXX_Size() int {
return xxx_messageInfo_ReadRequest.Size(m)
}
func (m *ReadRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ReadRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ReadRequest proto.InternalMessageInfo
func (m *ReadRequest) GetId() string {
if m != nil {
return m.Id
}
return ""
}
type ReadResponse struct {
User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ReadResponse) Reset() { *m = ReadResponse{} }
func (m *ReadResponse) String() string { return proto.CompactTextString(m) }
func (*ReadResponse) ProtoMessage() {}
func (*ReadResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{7}
}
func (m *ReadResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadResponse.Unmarshal(m, b)
}
func (m *ReadResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ReadResponse.Marshal(b, m, deterministic)
}
func (m *ReadResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ReadResponse.Merge(m, src)
}
func (m *ReadResponse) XXX_Size() int {
return xxx_messageInfo_ReadResponse.Size(m)
}
func (m *ReadResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ReadResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ReadResponse proto.InternalMessageInfo
func (m *ReadResponse) GetUser() *User {
if m != nil {
return m.User
}
return nil
}
type UpdateRequest struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UpdateRequest) Reset() { *m = UpdateRequest{} }
func (m *UpdateRequest) String() string { return proto.CompactTextString(m) }
func (*UpdateRequest) ProtoMessage() {}
func (*UpdateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{8}
}
func (m *UpdateRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UpdateRequest.Unmarshal(m, b)
}
func (m *UpdateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_UpdateRequest.Marshal(b, m, deterministic)
}
func (m *UpdateRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_UpdateRequest.Merge(m, src)
}
func (m *UpdateRequest) XXX_Size() int {
return xxx_messageInfo_UpdateRequest.Size(m)
}
func (m *UpdateRequest) XXX_DiscardUnknown() {
xxx_messageInfo_UpdateRequest.DiscardUnknown(m)
}
var xxx_messageInfo_UpdateRequest proto.InternalMessageInfo
func (m *UpdateRequest) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *UpdateRequest) GetUsername() string {
if m != nil {
return m.Username
}
return ""
}
func (m *UpdateRequest) GetEmail() string {
if m != nil {
return m.Email
}
return ""
}
type UpdateResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UpdateResponse) Reset() { *m = UpdateResponse{} }
func (m *UpdateResponse) String() string { return proto.CompactTextString(m) }
func (*UpdateResponse) ProtoMessage() {}
func (*UpdateResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{9}
}
func (m *UpdateResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UpdateResponse.Unmarshal(m, b)
}
func (m *UpdateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_UpdateResponse.Marshal(b, m, deterministic)
}
func (m *UpdateResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_UpdateResponse.Merge(m, src)
}
func (m *UpdateResponse) XXX_Size() int {
return xxx_messageInfo_UpdateResponse.Size(m)
}
func (m *UpdateResponse) XXX_DiscardUnknown() {
xxx_messageInfo_UpdateResponse.DiscardUnknown(m)
}
var xxx_messageInfo_UpdateResponse proto.InternalMessageInfo
type UpdatePasswordRequest struct {
UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"`
OldPassword string `protobuf:"bytes,2,opt,name=oldPassword,proto3" json:"oldPassword,omitempty"`
NewPassword string `protobuf:"bytes,3,opt,name=newPassword,proto3" json:"newPassword,omitempty"`
ConfirmPassword string `protobuf:"bytes,4,opt,name=confirm_password,json=confirmPassword,proto3" json:"confirm_password,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UpdatePasswordRequest) Reset() { *m = UpdatePasswordRequest{} }
func (m *UpdatePasswordRequest) String() string { return proto.CompactTextString(m) }
func (*UpdatePasswordRequest) ProtoMessage() {}
func (*UpdatePasswordRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{10}
}
func (m *UpdatePasswordRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UpdatePasswordRequest.Unmarshal(m, b)
}
func (m *UpdatePasswordRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_UpdatePasswordRequest.Marshal(b, m, deterministic)
}
func (m *UpdatePasswordRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_UpdatePasswordRequest.Merge(m, src)
}
func (m *UpdatePasswordRequest) XXX_Size() int {
return xxx_messageInfo_UpdatePasswordRequest.Size(m)
}
func (m *UpdatePasswordRequest) XXX_DiscardUnknown() {
xxx_messageInfo_UpdatePasswordRequest.DiscardUnknown(m)
}
var xxx_messageInfo_UpdatePasswordRequest proto.InternalMessageInfo
func (m *UpdatePasswordRequest) GetUserId() string {
if m != nil {
return m.UserId
}
return ""
}
func (m *UpdatePasswordRequest) GetOldPassword() string {
if m != nil {
return m.OldPassword
}
return ""
}
func (m *UpdatePasswordRequest) GetNewPassword() string {
if m != nil {
return m.NewPassword
}
return ""
}
func (m *UpdatePasswordRequest) GetConfirmPassword() string {
if m != nil {
return m.ConfirmPassword
}
return ""
}
type UpdatePasswordResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UpdatePasswordResponse) Reset() { *m = UpdatePasswordResponse{} }
func (m *UpdatePasswordResponse) String() string { return proto.CompactTextString(m) }
func (*UpdatePasswordResponse) ProtoMessage() {}
func (*UpdatePasswordResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{11}
}
func (m *UpdatePasswordResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UpdatePasswordResponse.Unmarshal(m, b)
}
func (m *UpdatePasswordResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_UpdatePasswordResponse.Marshal(b, m, deterministic)
}
func (m *UpdatePasswordResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_UpdatePasswordResponse.Merge(m, src)
}
func (m *UpdatePasswordResponse) XXX_Size() int {
return xxx_messageInfo_UpdatePasswordResponse.Size(m)
}
func (m *UpdatePasswordResponse) XXX_DiscardUnknown() {
xxx_messageInfo_UpdatePasswordResponse.DiscardUnknown(m)
}
var xxx_messageInfo_UpdatePasswordResponse proto.InternalMessageInfo
type SearchRequest struct {
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"`
Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"`
Offset int64 `protobuf:"varint,4,opt,name=offset,proto3" json:"offset,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SearchRequest) Reset() { *m = SearchRequest{} }
func (m *SearchRequest) String() string { return proto.CompactTextString(m) }
func (*SearchRequest) ProtoMessage() {}
func (*SearchRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{12}
}
func (m *SearchRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SearchRequest.Unmarshal(m, b)
}
func (m *SearchRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SearchRequest.Marshal(b, m, deterministic)
}
func (m *SearchRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_SearchRequest.Merge(m, src)
}
func (m *SearchRequest) XXX_Size() int {
return xxx_messageInfo_SearchRequest.Size(m)
}
func (m *SearchRequest) XXX_DiscardUnknown() {
xxx_messageInfo_SearchRequest.DiscardUnknown(m)
}
var xxx_messageInfo_SearchRequest proto.InternalMessageInfo
func (m *SearchRequest) GetUsername() string {
if m != nil {
return m.Username
}
return ""
}
func (m *SearchRequest) GetEmail() string {
if m != nil {
return m.Email
}
return ""
}
func (m *SearchRequest) GetLimit() int64 {
if m != nil {
return m.Limit
}
return 0
}
func (m *SearchRequest) GetOffset() int64 {
if m != nil {
return m.Offset
}
return 0
}
type SearchResponse struct {
Users []*User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SearchResponse) Reset() { *m = SearchResponse{} }
func (m *SearchResponse) String() string { return proto.CompactTextString(m) }
func (*SearchResponse) ProtoMessage() {}
func (*SearchResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{13}
}
func (m *SearchResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SearchResponse.Unmarshal(m, b)
}
func (m *SearchResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SearchResponse.Marshal(b, m, deterministic)
}
func (m *SearchResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_SearchResponse.Merge(m, src)
}
func (m *SearchResponse) XXX_Size() int {
return xxx_messageInfo_SearchResponse.Size(m)
}
func (m *SearchResponse) XXX_DiscardUnknown() {
xxx_messageInfo_SearchResponse.DiscardUnknown(m)
}
var xxx_messageInfo_SearchResponse proto.InternalMessageInfo
func (m *SearchResponse) GetUsers() []*User {
if m != nil {
return m.Users
}
return nil
}
type ReadSessionRequest struct {
SessionId string `protobuf:"bytes,1,opt,name=sessionId,proto3" json:"sessionId,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ReadSessionRequest) Reset() { *m = ReadSessionRequest{} }
func (m *ReadSessionRequest) String() string { return proto.CompactTextString(m) }
func (*ReadSessionRequest) ProtoMessage() {}
func (*ReadSessionRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{14}
}
func (m *ReadSessionRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadSessionRequest.Unmarshal(m, b)
}
func (m *ReadSessionRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ReadSessionRequest.Marshal(b, m, deterministic)
}
func (m *ReadSessionRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ReadSessionRequest.Merge(m, src)
}
func (m *ReadSessionRequest) XXX_Size() int {
return xxx_messageInfo_ReadSessionRequest.Size(m)
}
func (m *ReadSessionRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ReadSessionRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ReadSessionRequest proto.InternalMessageInfo
func (m *ReadSessionRequest) GetSessionId() string {
if m != nil {
return m.SessionId
}
return ""
}
type ReadSessionResponse struct {
Session *Session `protobuf:"bytes,1,opt,name=session,proto3" json:"session,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ReadSessionResponse) Reset() { *m = ReadSessionResponse{} }
func (m *ReadSessionResponse) String() string { return proto.CompactTextString(m) }
func (*ReadSessionResponse) ProtoMessage() {}
func (*ReadSessionResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{15}
}
func (m *ReadSessionResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadSessionResponse.Unmarshal(m, b)
}
func (m *ReadSessionResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ReadSessionResponse.Marshal(b, m, deterministic)
}
func (m *ReadSessionResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ReadSessionResponse.Merge(m, src)
}
func (m *ReadSessionResponse) XXX_Size() int {
return xxx_messageInfo_ReadSessionResponse.Size(m)
}
func (m *ReadSessionResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ReadSessionResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ReadSessionResponse proto.InternalMessageInfo
func (m *ReadSessionResponse) GetSession() *Session {
if m != nil {
return m.Session
}
return nil
}
type LoginRequest struct {
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *LoginRequest) Reset() { *m = LoginRequest{} }
func (m *LoginRequest) String() string { return proto.CompactTextString(m) }
func (*LoginRequest) ProtoMessage() {}
func (*LoginRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{16}
}
func (m *LoginRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LoginRequest.Unmarshal(m, b)
}
func (m *LoginRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_LoginRequest.Marshal(b, m, deterministic)
}
func (m *LoginRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_LoginRequest.Merge(m, src)
}
func (m *LoginRequest) XXX_Size() int {
return xxx_messageInfo_LoginRequest.Size(m)
}
func (m *LoginRequest) XXX_DiscardUnknown() {
xxx_messageInfo_LoginRequest.DiscardUnknown(m)
}
var xxx_messageInfo_LoginRequest proto.InternalMessageInfo
func (m *LoginRequest) GetUsername() string {
if m != nil {
return m.Username
}
return ""
}
func (m *LoginRequest) GetEmail() string {
if m != nil {
return m.Email
}
return ""
}
func (m *LoginRequest) GetPassword() string {
if m != nil {
return m.Password
}
return ""
}
type LoginResponse struct {
Session *Session `protobuf:"bytes,1,opt,name=session,proto3" json:"session,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *LoginResponse) Reset() { *m = LoginResponse{} }
func (m *LoginResponse) String() string { return proto.CompactTextString(m) }
func (*LoginResponse) ProtoMessage() {}
func (*LoginResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{17}
}
func (m *LoginResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LoginResponse.Unmarshal(m, b)
}
func (m *LoginResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_LoginResponse.Marshal(b, m, deterministic)
}
func (m *LoginResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_LoginResponse.Merge(m, src)
}
func (m *LoginResponse) XXX_Size() int {
return xxx_messageInfo_LoginResponse.Size(m)
}
func (m *LoginResponse) XXX_DiscardUnknown() {
xxx_messageInfo_LoginResponse.DiscardUnknown(m)
}
var xxx_messageInfo_LoginResponse proto.InternalMessageInfo
func (m *LoginResponse) GetSession() *Session {
if m != nil {
return m.Session
}
return nil
}
type LogoutRequest struct {
SessionId string `protobuf:"bytes,1,opt,name=sessionId,proto3" json:"sessionId,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *LogoutRequest) Reset() { *m = LogoutRequest{} }
func (m *LogoutRequest) String() string { return proto.CompactTextString(m) }
func (*LogoutRequest) ProtoMessage() {}
func (*LogoutRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{18}
}
func (m *LogoutRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LogoutRequest.Unmarshal(m, b)
}
func (m *LogoutRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_LogoutRequest.Marshal(b, m, deterministic)
}
func (m *LogoutRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_LogoutRequest.Merge(m, src)
}
func (m *LogoutRequest) XXX_Size() int {
return xxx_messageInfo_LogoutRequest.Size(m)
}
func (m *LogoutRequest) XXX_DiscardUnknown() {
xxx_messageInfo_LogoutRequest.DiscardUnknown(m)
}
var xxx_messageInfo_LogoutRequest proto.InternalMessageInfo
func (m *LogoutRequest) GetSessionId() string {
if m != nil {
return m.SessionId
}
return ""
}
type LogoutResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *LogoutResponse) Reset() { *m = LogoutResponse{} }
func (m *LogoutResponse) String() string { return proto.CompactTextString(m) }
func (*LogoutResponse) ProtoMessage() {}
func (*LogoutResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_b1c161a4c7514913, []int{19}
}
func (m *LogoutResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LogoutResponse.Unmarshal(m, b)
}
func (m *LogoutResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_LogoutResponse.Marshal(b, m, deterministic)
}
func (m *LogoutResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_LogoutResponse.Merge(m, src)
}
func (m *LogoutResponse) XXX_Size() int {
return xxx_messageInfo_LogoutResponse.Size(m)
}
func (m *LogoutResponse) XXX_DiscardUnknown() {
xxx_messageInfo_LogoutResponse.DiscardUnknown(m)
}
var xxx_messageInfo_LogoutResponse proto.InternalMessageInfo
func init() {
proto.RegisterType((*User)(nil), "User")
proto.RegisterType((*Session)(nil), "Session")
proto.RegisterType((*CreateRequest)(nil), "CreateRequest")
proto.RegisterType((*CreateResponse)(nil), "CreateResponse")
proto.RegisterType((*DeleteRequest)(nil), "DeleteRequest")
proto.RegisterType((*DeleteResponse)(nil), "DeleteResponse")
proto.RegisterType((*ReadRequest)(nil), "ReadRequest")
proto.RegisterType((*ReadResponse)(nil), "ReadResponse")
proto.RegisterType((*UpdateRequest)(nil), "UpdateRequest")
proto.RegisterType((*UpdateResponse)(nil), "UpdateResponse")
proto.RegisterType((*UpdatePasswordRequest)(nil), "UpdatePasswordRequest")
proto.RegisterType((*UpdatePasswordResponse)(nil), "UpdatePasswordResponse")
proto.RegisterType((*SearchRequest)(nil), "SearchRequest")
proto.RegisterType((*SearchResponse)(nil), "SearchResponse")
proto.RegisterType((*ReadSessionRequest)(nil), "ReadSessionRequest")
proto.RegisterType((*ReadSessionResponse)(nil), "ReadSessionResponse")
proto.RegisterType((*LoginRequest)(nil), "LoginRequest")
proto.RegisterType((*LoginResponse)(nil), "LoginResponse")
proto.RegisterType((*LogoutRequest)(nil), "LogoutRequest")
proto.RegisterType((*LogoutResponse)(nil), "LogoutResponse")
}
func init() {
proto.RegisterFile("proto/users.proto", fileDescriptor_b1c161a4c7514913)
}
var fileDescriptor_b1c161a4c7514913 = []byte{
// 600 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x55, 0xcf, 0x6e, 0xd3, 0x4e,
0x10, 0x4e, 0xe2, 0x38, 0x69, 0x27, 0x71, 0xda, 0xdf, 0xb6, 0xbf, 0x60, 0x0c, 0x88, 0x6a, 0x25,
0xa4, 0x56, 0xa8, 0x8b, 0x94, 0x9e, 0xe0, 0x5a, 0x2e, 0x48, 0x1c, 0xc0, 0x55, 0x6f, 0x48, 0xc8,
0x24, 0x1b, 0xb0, 0x14, 0x7b, 0x8d, 0xd7, 0x51, 0x39, 0x20, 0xf1, 0x26, 0x3c, 0x08, 0x4f, 0xc7,
0xec, 0xbf, 0xc4, 0x36, 0x8d, 0x04, 0xa8, 0xdc, 0x3c, 0x33, 0xdf, 0xec, 0x7c, 0x3b, 0x33, 0xdf,
0x1a, 0xfe, 0x2b, 0x4a, 0x51, 0x89, 0x67, 0x6b, 0xc9, 0x4b, 0xc9, 0xf4, 0x37, 0xfd, 0x0a, 0xfd,
0x6b, 0x34, 0xc9, 0x04, 0x7a, 0xe9, 0x22, 0xec, 0x9e, 0x74, 0x4f, 0xf7, 0x63, 0xfc, 0x22, 0x11,
0xec, 0x29, 0x58, 0x9e, 0x64, 0x3c, 0xec, 0x69, 0xef, 0xc6, 0x26, 0xc7, 0xe0, 0xf3, 0x2c, 0x49,
0x57, 0xa1, 0xa7, 0x03, 0xc6, 0x20, 0x21, 0x0c, 0xe7, 0x25, 0x4f, 0x2a, 0xbe, 0x08, 0xfb, 0xe8,
0xf7, 0x62, 0x67, 0xaa, 0xc8, 0xba, 0x58, 0xe8, 0x88, 0x6f, 0x22, 0xd6, 0xa4, 0xdf, 0x60, 0x78,
0xc5, 0xa5, 0x4c, 0x45, 0xfe, 0xaf, 0x09, 0xf0, 0x2f, 0x45, 0x5a, 0x72, 0xe9, 0x08, 0x58, 0x93,
0x66, 0x10, 0x5c, 0x6a, 0x50, 0xcc, 0x3f, 0xaf, 0xb9, 0xac, 0xee, 0x80, 0x06, 0x66, 0x14, 0x89,
0x94, 0x37, 0xa2, 0x34, 0x3c, 0x30, 0xc3, 0xd9, 0xf4, 0x10, 0x26, 0xae, 0x9c, 0x2c, 0x44, 0x2e,
0x39, 0x7d, 0x0c, 0xc1, 0x4b, 0xbe, 0xe2, 0x3b, 0x09, 0xa8, 0x14, 0x07, 0xb0, 0x29, 0x8f, 0x60,
0x14, 0xf3, 0x64, 0xb1, 0x2b, 0xe1, 0x0c, 0xc6, 0x26, 0x6c, 0xe0, 0xe4, 0x3e, 0xf4, 0x15, 0x63,
0x8d, 0x18, 0xcd, 0x7c, 0xa6, 0xc6, 0x1d, 0x6b, 0x17, 0x7d, 0x0b, 0xc1, 0xb5, 0x9e, 0xc4, 0x9d,
0xdd, 0x5e, 0xd1, 0x75, 0x47, 0x5a, 0xba, 0xdf, 0xbb, 0xf0, 0xbf, 0x71, 0xbd, 0xb1, 0x6d, 0x70,
0xd5, 0xa6, 0x30, 0x50, 0xa7, 0xbd, 0x72, 0x15, 0xad, 0x45, 0x4e, 0x60, 0x24, 0x56, 0x0b, 0x87,
0xb6, 0x85, 0xeb, 0x2e, 0x85, 0xc8, 0xf9, 0xcd, 0x06, 0x61, 0x18, 0xd4, 0x5d, 0xe4, 0x0c, 0x0e,
0xe7, 0x22, 0x5f, 0xa6, 0x65, 0xf6, 0xbe, 0x35, 0x8d, 0x03, 0xeb, 0x77, 0x50, 0x1a, 0xc2, 0xb4,
0xcd, 0xcf, 0x52, 0x17, 0x10, 0x5c, 0xf1, 0xa4, 0x9c, 0x7f, 0x72, 0x8c, 0xeb, 0xfd, 0xe8, 0xee,
0xea, 0x47, 0xaf, 0xbe, 0x0d, 0xe8, 0x5d, 0xa5, 0x59, 0x5a, 0x69, 0x8e, 0x5e, 0x6c, 0x0c, 0x75,
0x73, 0xb1, 0x5c, 0x4a, 0x5e, 0xd9, 0x4d, 0xb5, 0x16, 0x3d, 0x87, 0x89, 0x2b, 0x68, 0xa7, 0xf7,
0x00, 0x7c, 0x2d, 0x57, 0x2c, 0xe7, 0x6d, 0xc7, 0x67, 0x7c, 0x74, 0x06, 0x44, 0x8d, 0xda, 0x4a,
0xc8, 0x91, 0x7c, 0x08, 0xfb, 0xd2, 0x78, 0x36, 0x9d, 0xdd, 0x3a, 0xe8, 0x73, 0x38, 0x6a, 0xe4,
0xd8, 0x3a, 0x14, 0x86, 0x16, 0x63, 0x17, 0x65, 0x8f, 0x39, 0x88, 0x0b, 0xd0, 0x77, 0x30, 0x7e,
0x2d, 0x3e, 0xa6, 0xf9, 0xdf, 0x77, 0xa3, 0xae, 0x0d, 0xaf, 0xa5, 0x8d, 0x0b, 0x08, 0xec, 0xe9,
0x7f, 0x40, 0xe9, 0x5c, 0x27, 0x89, 0x75, 0xf5, 0x7b, 0x97, 0xc7, 0xed, 0x74, 0x70, 0x53, 0x64,
0xf6, 0xc3, 0x03, 0x5f, 0xb5, 0x54, 0x92, 0xa7, 0x30, 0x30, 0xda, 0x24, 0x13, 0xd6, 0x78, 0x13,
0xa2, 0x03, 0xd6, 0x12, 0x6d, 0x87, 0x3c, 0x81, 0xbe, 0xea, 0x22, 0x19, 0xb3, 0x9a, 0x14, 0xa3,
0x80, 0xd5, 0x95, 0x87, 0x30, 0x3c, 0xd3, 0xac, 0x16, 0x9e, 0xd9, 0x50, 0x1a, 0x9e, 0xd9, 0x92,
0x89, 0x06, 0x1b, 0xa5, 0x23, 0xb8, 0xf1, 0x26, 0x20, 0xb8, 0xf5, 0x04, 0x68, 0xb0, 0xd9, 0x14,
0x04, 0x37, 0x76, 0x14, 0xc1, 0xcd, 0x15, 0x42, 0xf0, 0xa5, 0x13, 0xe5, 0x46, 0x1e, 0x53, 0x76,
0xab, 0x24, 0xa3, 0x7b, 0x6c, 0x87, 0x14, 0x3a, 0xe4, 0x14, 0x7c, 0x3d, 0x1f, 0x12, 0xb0, 0xfa,
0x16, 0x44, 0x13, 0xd6, 0x18, 0x9b, 0xe1, 0x66, 0xba, 0x4c, 0x74, 0x6c, 0x3b, 0x1d, 0xe4, 0xd6,
0x6c, 0x3f, 0x82, 0x5f, 0x98, 0xd7, 0xcc, 0xfd, 0x06, 0x8e, 0xd8, 0xaf, 0x1b, 0x1d, 0x1d, 0xb3,
0x5b, 0x56, 0x96, 0x76, 0x3e, 0x0c, 0xf4, 0x3f, 0xec, 0xe2, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff,
0xc7, 0x99, 0xc1, 0x0c, 0xd8, 0x06, 0x00, 0x00,
}

View File

@@ -0,0 +1,229 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: proto/users.proto
package users
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
import (
context "context"
api "github.com/micro/micro/v3/service/api"
client "github.com/micro/micro/v3/service/client"
server "github.com/micro/micro/v3/service/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Reference imports to suppress errors if they are not otherwise used.
var _ api.Endpoint
var _ context.Context
var _ client.Option
var _ server.Option
// Api Endpoints for Users service
func NewUsersEndpoints() []*api.Endpoint {
return []*api.Endpoint{}
}
// Client API for Users service
type UsersService interface {
Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error)
Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error)
Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error)
Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error)
Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error)
UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, opts ...client.CallOption) (*UpdatePasswordResponse, error)
Login(ctx context.Context, in *LoginRequest, opts ...client.CallOption) (*LoginResponse, error)
Logout(ctx context.Context, in *LogoutRequest, opts ...client.CallOption) (*LogoutResponse, error)
ReadSession(ctx context.Context, in *ReadSessionRequest, opts ...client.CallOption) (*ReadSessionResponse, error)
}
type usersService struct {
c client.Client
name string
}
func NewUsersService(name string, c client.Client) UsersService {
return &usersService{
c: c,
name: name,
}
}
func (c *usersService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) {
req := c.c.NewRequest(c.name, "Users.Create", in)
out := new(CreateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *usersService) Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error) {
req := c.c.NewRequest(c.name, "Users.Read", in)
out := new(ReadResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *usersService) Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error) {
req := c.c.NewRequest(c.name, "Users.Update", in)
out := new(UpdateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *usersService) Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error) {
req := c.c.NewRequest(c.name, "Users.Delete", in)
out := new(DeleteResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *usersService) Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error) {
req := c.c.NewRequest(c.name, "Users.Search", in)
out := new(SearchResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *usersService) UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, opts ...client.CallOption) (*UpdatePasswordResponse, error) {
req := c.c.NewRequest(c.name, "Users.UpdatePassword", in)
out := new(UpdatePasswordResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *usersService) Login(ctx context.Context, in *LoginRequest, opts ...client.CallOption) (*LoginResponse, error) {
req := c.c.NewRequest(c.name, "Users.Login", in)
out := new(LoginResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *usersService) Logout(ctx context.Context, in *LogoutRequest, opts ...client.CallOption) (*LogoutResponse, error) {
req := c.c.NewRequest(c.name, "Users.Logout", in)
out := new(LogoutResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *usersService) ReadSession(ctx context.Context, in *ReadSessionRequest, opts ...client.CallOption) (*ReadSessionResponse, error) {
req := c.c.NewRequest(c.name, "Users.ReadSession", in)
out := new(ReadSessionResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Users service
type UsersHandler interface {
Create(context.Context, *CreateRequest, *CreateResponse) error
Read(context.Context, *ReadRequest, *ReadResponse) error
Update(context.Context, *UpdateRequest, *UpdateResponse) error
Delete(context.Context, *DeleteRequest, *DeleteResponse) error
Search(context.Context, *SearchRequest, *SearchResponse) error
UpdatePassword(context.Context, *UpdatePasswordRequest, *UpdatePasswordResponse) error
Login(context.Context, *LoginRequest, *LoginResponse) error
Logout(context.Context, *LogoutRequest, *LogoutResponse) error
ReadSession(context.Context, *ReadSessionRequest, *ReadSessionResponse) error
}
func RegisterUsersHandler(s server.Server, hdlr UsersHandler, opts ...server.HandlerOption) error {
type users interface {
Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error
Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error
Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error
Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error
Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error
UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, out *UpdatePasswordResponse) error
Login(ctx context.Context, in *LoginRequest, out *LoginResponse) error
Logout(ctx context.Context, in *LogoutRequest, out *LogoutResponse) error
ReadSession(ctx context.Context, in *ReadSessionRequest, out *ReadSessionResponse) error
}
type Users struct {
users
}
h := &usersHandler{hdlr}
return s.Handle(s.NewHandler(&Users{h}, opts...))
}
type usersHandler struct {
UsersHandler
}
func (h *usersHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error {
return h.UsersHandler.Create(ctx, in, out)
}
func (h *usersHandler) Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error {
return h.UsersHandler.Read(ctx, in, out)
}
func (h *usersHandler) Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error {
return h.UsersHandler.Update(ctx, in, out)
}
func (h *usersHandler) Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error {
return h.UsersHandler.Delete(ctx, in, out)
}
func (h *usersHandler) Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error {
return h.UsersHandler.Search(ctx, in, out)
}
func (h *usersHandler) UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, out *UpdatePasswordResponse) error {
return h.UsersHandler.UpdatePassword(ctx, in, out)
}
func (h *usersHandler) Login(ctx context.Context, in *LoginRequest, out *LoginResponse) error {
return h.UsersHandler.Login(ctx, in, out)
}
func (h *usersHandler) Logout(ctx context.Context, in *LogoutRequest, out *LogoutResponse) error {
return h.UsersHandler.Logout(ctx, in, out)
}
func (h *usersHandler) ReadSession(ctx context.Context, in *ReadSessionRequest, out *ReadSessionResponse) error {
return h.UsersHandler.ReadSession(ctx, in, out)
}

View File

@@ -0,0 +1,110 @@
syntax = "proto3";
service Users {
rpc Create(CreateRequest) returns (CreateResponse) {}
rpc Read(ReadRequest) returns (ReadResponse) {}
rpc Update(UpdateRequest) returns (UpdateResponse) {}
rpc Delete(DeleteRequest) returns (DeleteResponse) {}
rpc Search(SearchRequest) returns (SearchResponse) {}
rpc UpdatePassword(UpdatePasswordRequest) returns (UpdatePasswordResponse) {}
rpc Login(LoginRequest) returns (LoginResponse) {}
rpc Logout(LogoutRequest) returns (LogoutResponse) {}
rpc ReadSession(ReadSessionRequest) returns(ReadSessionResponse) {}
}
message User {
string id = 1; // uuid
string username = 2; // alphanumeric user or org
string email = 3;
int64 created = 4; // unix
int64 updated = 5; // unix
}
message Session {
string id = 1;
string username = 2;
string email = 3;
int64 created = 4; // unix
int64 expires = 5; // unix
}
message CreateRequest {
string id = 1; // uuid
string username = 2; // alphanumeric user or org
string email = 3;
string password = 4;
}
message CreateResponse {
}
message DeleteRequest {
string id = 1;
}
message DeleteResponse {
}
message ReadRequest {
string id = 1;
}
message ReadResponse {
User user = 1;
}
message UpdateRequest {
string id = 1; // uuid
string username = 2; // alphanumeric user or org
string email = 3;
}
message UpdateResponse {
}
message UpdatePasswordRequest {
string userId = 1;
string oldPassword = 2;
string newPassword = 3;
string confirm_password = 4;
}
message UpdatePasswordResponse {
}
message SearchRequest {
string username = 1;
string email = 2;
int64 limit = 3;
int64 offset = 4;
}
message SearchResponse {
repeated User users = 1;
}
message ReadSessionRequest {
string sessionId = 1;
}
message ReadSessionResponse {
Session session = 1;
}
message LoginRequest {
string username = 1;
string email = 2;
string password = 3;
}
message LoginResponse {
Session session = 1;
}
message LogoutRequest {
string sessionId = 1;
}
message LogoutResponse {
}

1
users/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
users

View File

@@ -1,3 +1,3 @@
FROM alpine:3.2
FROM alpine
ADD users /users
ENTRYPOINT [ "/users" ]

View File

@@ -7,13 +7,8 @@ init:
go get github.com/micro/micro/v3/cmd/protoc-gen-micro
.PHONY: proto
proto:
protoc --openapi_out=. --proto_path=. --micro_out=. --go_out=:. proto/users.proto
.PHONY: docs
docs:
protoc --openapi_out=. --proto_path=. --micro_out=. --go_out=:. proto/users.proto
@redoc-cli bundle api-users.json
protoc --proto_path=. --micro_out=. --go_out=:. proto/users.proto
.PHONY: build
build:
go build -o users *.go

View File

@@ -1,76 +1,23 @@
A user service for storing accounts and simple auth.
# Users Service
The users service provides user management and authentication so you can easily add them to your own apps
without having to build the entire thing from scratch.
This is the Users service
## Getting started
Generated with
```
micro run github.com/micro/services/users
micro new users
```
## Usage
User server implements the following RPC Methods
Generate the proto code
Users
- Create
- Read
- Update
- Delete
- Search
- UpdatePassword
- Login
- Logout
- ReadSession
### Create
```shell
micro call users Users.Create '{"id": "ff3c06de-9e43-41c7-9bab-578f6b4ad32b", "username": "asim", "email": "asim@example.com", "password": "password1"}'
```
make proto
```
### Read
Run the service
```shell
micro call users Users.Read '{"id": "ff3c06de-9e43-41c7-9bab-578f6b4ad32b"}'
```
### Update
```shell
micro call users Users.Update '{"id": "ff3c06de-9e43-41c7-9bab-578f6b4ad32b", "username": "asim", "email": "asim+update@example.com"}'
```
### Update Password
```shell
micro call users Users.UpdatePassword '{"userId": "ff3c06de-9e43-41c7-9bab-578f6b4ad32b", "oldPassword": "password1", "newPassword": "newpassword1", "confirmPassword": "newpassword1" }'
```
### Delete
```shell
micro call users Users.Delete '{"id": "ff3c06de-9e43-41c7-9bab-578f6b4ad32b"}'
```
### Login
```shell
micro call users Users.Login '{"username": "asim", "password": "password1"}'
```
### Read Session
```shell
micro call users Users.ReadSession '{"sessionId": "sr7UEBmIMg5hYOgiljnhrd4XLsnalNewBV9KzpZ9aD8w37b3jRmEujGtKGcGlXPg1yYoSHR3RLy66ugglw0tofTNGm57NrNYUHsFxfwuGC6pvCn8BecB7aEF6UxTyVFq"}'
```
### Logout
```shell
micro call users Users.Logout '{"sessionId": "sr7UEBmIMg5hYOgiljnhrd4XLsnalNewBV9KzpZ9aD8w37b3jRmEujGtKGcGlXPg1yYoSHR3RLy66ugglw0tofTNGm57NrNYUHsFxfwuGC6pvCn8BecB7aEF6UxTyVFq"}'
```
micro run .
```

View File

@@ -1,3 +1,2 @@
package main
//go:generate make proto

View File

@@ -1,174 +1,369 @@
package handler
import (
"crypto/rand"
"encoding/base64"
"context"
"regexp"
"strings"
"time"
"github.com/google/uuid"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/services/users/domain"
"github.com/micro/micro/v3/service/logger"
pb "github.com/micro/services/users/proto"
"golang.org/x/crypto/bcrypt"
"golang.org/x/net/context"
)
const (
x = "cruft123"
"gorm.io/gorm"
)
var (
alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
ErrMissingFirstName = errors.BadRequest("MISSING_FIRST_NAME", "Missing first name")
ErrMissingLastName = errors.BadRequest("MISSING_LAST_NAME", "Missing last name")
ErrMissingEmail = errors.BadRequest("MISSING_EMAIL", "Missing email")
ErrDuplicateEmail = errors.BadRequest("DUPLICATE_EMAIL", "A user with this email address already exists")
ErrInvalidEmail = errors.BadRequest("INVALID_EMAIL", "The email provided is invalid")
ErrInvalidPassword = errors.BadRequest("INVALID_PASSWORD", "Password must be at least 8 characters long")
ErrMissingIDs = errors.BadRequest("MISSING_IDS", "One or more ids are required")
ErrMissingID = errors.BadRequest("MISSING_ID", "Missing ID")
ErrMissingToken = errors.BadRequest("MISSING_TOKEN", "Missing token")
ErrIncorrectPassword = errors.BadRequest("INCORRECT_PASSWORD", "Incorrect password")
ErrTokenExpired = errors.BadRequest("TOKEN_EXPIRED", "Token has expired")
ErrInvalidToken = errors.BadRequest("INVALID_TOKEN", "Token is invalid")
ErrNotFound = errors.NotFound("NOT_FOUND", "User not found")
emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
tokenTTL = time.Hour * 7 * 24
)
func random(i int) string {
bytes := make([]byte, i)
for {
rand.Read(bytes)
for i, b := range bytes {
bytes[i] = alphanum[b%byte(len(alphanum))]
}
return string(bytes)
type User struct {
ID string
FirstName string
LastName string
Email string `gorm:"uniqueIndex"`
Password string
CreatedAt time.Time
Tokens []Token
}
func (u *User) Serialize() *pb.User {
return &pb.User{
Id: u.ID,
FirstName: u.FirstName,
LastName: u.LastName,
Email: u.Email,
}
return "ughwhy?!!!"
}
type Token struct {
Key string `gorm:"primaryKey"`
CreatedAt time.Time
ExpiresAt time.Time
UserID string
User User
}
type Users struct {
domain *domain.Domain
DB *gorm.DB
Time func() time.Time
}
func NewUsers() *Users {
return &Users{
domain: domain.New(),
// Create a user
func (u *Users) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
// validate the request
if len(req.FirstName) == 0 {
return ErrMissingFirstName
}
if len(req.LastName) == 0 {
return ErrMissingLastName
}
if len(req.Email) == 0 {
return ErrMissingEmail
}
if !isEmailValid(req.Email) {
return ErrInvalidEmail
}
}
func (s *Users) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
if len(req.Password) < 8 {
return errors.InternalServerError("users.Create.Check", "Password is less than 8 characters")
return ErrInvalidPassword
}
salt := random(16)
h, err := bcrypt.GenerateFromPassword([]byte(x+salt+req.Password), 10)
// hash and salt the password using bcrypt
phash, err := hashAndSalt(req.Password)
if err != nil {
return errors.InternalServerError("users.Create", err.Error())
logger.Errorf("Error hasing and salting password: %v", err)
return errors.InternalServerError("HASHING_ERROR", "Error hashing password")
}
pp := base64.StdEncoding.EncodeToString(h)
return s.domain.Create(&pb.User{
Id: req.Id,
Username: strings.ToLower(req.Username),
Email: strings.ToLower(req.Email),
}, salt, pp)
}
return u.DB.Transaction(func(tx *gorm.DB) error {
// write the user to the database
user := &User{
ID: uuid.New().String(),
FirstName: req.FirstName,
LastName: req.LastName,
Email: req.Email,
Password: phash,
}
err = u.DB.Create(user).Error
if err != nil && strings.Contains(err.Error(), "idx_users_email") {
return ErrDuplicateEmail
} else if err != nil {
logger.Errorf("Error writing to the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
func (s *Users) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
user, err := s.domain.Read(req.Id)
if err != nil {
return err
}
rsp.User = user
return nil
}
// generate a token for the user
token := Token{
UserID: user.ID,
Key: uuid.New().String(),
ExpiresAt: u.Time().Add(time.Hour * 24 * 7),
}
if err := tx.Create(&token).Error; err != nil {
logger.Errorf("Error writing to the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
func (s *Users) Update(ctx context.Context, req *pb.UpdateRequest, rsp *pb.UpdateResponse) error {
return s.domain.Update(&pb.User{
Id: req.Id,
Username: strings.ToLower(req.Username),
Email: strings.ToLower(req.Email),
// serialize the response
rsp.User = user.Serialize()
rsp.Token = token.Key
return nil
})
}
func (s *Users) Delete(ctx context.Context, req *pb.DeleteRequest, rsp *pb.DeleteResponse) error {
return s.domain.Delete(req.Id)
}
func (s *Users) Search(ctx context.Context, req *pb.SearchRequest, rsp *pb.SearchResponse) error {
users, err := s.domain.Search(req.Username, req.Email, req.Limit, req.Offset)
if err != nil {
return err
}
rsp.Users = users
return nil
}
func (s *Users) UpdatePassword(ctx context.Context, req *pb.UpdatePasswordRequest, rsp *pb.UpdatePasswordResponse) error {
usr, err := s.domain.Read(req.UserId)
if err != nil {
return errors.InternalServerError("users.updatepassword", err.Error())
}
if req.NewPassword != req.ConfirmPassword {
return errors.InternalServerError("users.updatepassword", "Passwords don't math")
// Read users using ID
func (u *Users) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
// validate the request
if len(req.Ids) == 0 {
return ErrMissingIDs
}
salt, hashed, err := s.domain.SaltAndPassword(usr.Username, usr.Email)
if err != nil {
return errors.InternalServerError("users.updatepassword", err.Error())
// query the database
var users []User
if err := u.DB.Model(&User{}).Where("id IN (?)", req.Ids).Find(&users).Error; err != nil {
logger.Errorf("Error reading from the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
hh, err := base64.StdEncoding.DecodeString(hashed)
if err != nil {
return errors.InternalServerError("users.updatepassword", err.Error())
}
if err := bcrypt.CompareHashAndPassword(hh, []byte(x+salt+req.OldPassword)); err != nil {
return errors.Unauthorized("users.updatepassword", err.Error())
}
salt = random(16)
h, err := bcrypt.GenerateFromPassword([]byte(x+salt+req.NewPassword), 10)
if err != nil {
return errors.InternalServerError("users.updatepassword", err.Error())
}
pp := base64.StdEncoding.EncodeToString(h)
if err := s.domain.UpdatePassword(req.UserId, salt, pp); err != nil {
return errors.InternalServerError("users.updatepassword", err.Error())
// serialize the response
rsp.Users = make(map[string]*pb.User, len(users))
for _, u := range users {
rsp.Users[u.ID] = u.Serialize()
}
return nil
}
func (s *Users) Login(ctx context.Context, req *pb.LoginRequest, rsp *pb.LoginResponse) error {
username := strings.ToLower(req.Username)
email := strings.ToLower(req.Email)
salt, hashed, err := s.domain.SaltAndPassword(username, email)
if err != nil {
return err
// Update a user
func (u *Users) Update(ctx context.Context, req *pb.UpdateRequest, rsp *pb.UpdateResponse) error {
// validate the request
if len(req.Id) == 0 {
return ErrMissingID
}
if req.FirstName != nil && len(req.FirstName.Value) == 0 {
return ErrMissingFirstName
}
if req.LastName != nil && len(req.LastName.Value) == 0 {
return ErrMissingLastName
}
if req.Email != nil && len(req.Email.Value) == 0 {
return ErrMissingEmail
}
if req.Email != nil && !isEmailValid(req.Email.Value) {
return ErrInvalidEmail
}
hh, err := base64.StdEncoding.DecodeString(hashed)
if err != nil {
return errors.InternalServerError("users.Login", err.Error())
// lookup the user
var user User
if err := u.DB.Where(&User{ID: req.Id}).First(&user).Error; err == gorm.ErrRecordNotFound {
return ErrNotFound
} else if err != nil {
logger.Errorf("Error reading from the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
if err := bcrypt.CompareHashAndPassword(hh, []byte(x+salt+req.Password)); err != nil {
return errors.Unauthorized("users.login", err.Error())
// assign the updated values
if req.FirstName != nil {
user.FirstName = req.FirstName.Value
}
// save session
sess := &pb.Session{
Id: random(128),
Username: username,
Email: email,
Created: time.Now().Unix(),
Expires: time.Now().Add(time.Hour * 24 * 7).Unix(),
if req.LastName != nil {
user.LastName = req.LastName.Value
}
if req.Email != nil {
user.Email = req.Email.Value
}
if err := s.domain.CreateSession(sess); err != nil {
return errors.InternalServerError("users.Login", err.Error())
// write the user to the database
err := u.DB.Save(user).Error
if err != nil && strings.Contains(err.Error(), "idx_users_email") {
return ErrDuplicateEmail
} else if err != nil {
logger.Errorf("Error writing to the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
rsp.Session = sess
// serialize the user
rsp.User = user.Serialize()
return nil
}
func (s *Users) Logout(ctx context.Context, req *pb.LogoutRequest, rsp *pb.LogoutResponse) error {
return s.domain.DeleteSession(req.SessionId)
// Delete a user
func (u *Users) Delete(ctx context.Context, req *pb.DeleteRequest, rsp *pb.DeleteResponse) error {
// validate the request
if len(req.Id) == 0 {
return ErrMissingID
}
// delete the users tokens
return u.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Delete(&Token{}, &Token{UserID: req.Id}).Error; err != nil {
logger.Errorf("Error writing to the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
// delete from the database
if err := tx.Delete(&User{}, &User{ID: req.Id}).Error; err != nil {
logger.Errorf("Error writing to the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
return nil
})
}
func (s *Users) ReadSession(ctx context.Context, req *pb.ReadSessionRequest, rsp *pb.ReadSessionResponse) error {
sess, err := s.domain.ReadSession(req.SessionId)
if err != nil {
return err
// List all users
func (u *Users) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error {
// query the database
var users []User
if err := u.DB.Model(&User{}).Find(&users).Error; err != nil {
logger.Errorf("Error reading from the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
// serialize the response
rsp.Users = make([]*pb.User, len(users))
for i, u := range users {
rsp.Users[i] = u.Serialize()
}
rsp.Session = sess
return nil
}
// Login using email and password returns the users profile and a token
func (u *Users) Login(ctx context.Context, req *pb.LoginRequest, rsp *pb.LoginResponse) error {
// validate the request
if len(req.Email) == 0 {
return ErrMissingEmail
}
if len(req.Password) == 0 {
return ErrInvalidPassword
}
return u.DB.Transaction(func(tx *gorm.DB) error {
// lookup the user
var user User
if err := tx.Where(&User{Email: req.Email}).First(&user).Error; err == gorm.ErrRecordNotFound {
return ErrNotFound
} else if err != nil {
logger.Errorf("Error reading from the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
// compare the passwords
if !passwordsMatch(user.Password, req.Password) {
return ErrIncorrectPassword
}
// generate a token for the user
token := Token{
UserID: user.ID,
Key: uuid.New().String(),
ExpiresAt: u.Time().Add(tokenTTL),
}
if err := tx.Create(&token).Error; err != nil {
logger.Errorf("Error writing to the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
// serialize the response
rsp.Token = token.Key
rsp.User = user.Serialize()
return nil
})
}
// Logout expires all tokens for the user
func (u *Users) Logout(ctx context.Context, req *pb.LogoutRequest, rsp *pb.LogoutResponse) error {
// validate the request
if len(req.Id) == 0 {
return ErrMissingID
}
return u.DB.Transaction(func(tx *gorm.DB) error {
// lookup the user
var user User
if err := tx.Where(&User{ID: req.Id}).Preload("Tokens").First(&user).Error; err == gorm.ErrRecordNotFound {
return ErrNotFound
} else if err != nil {
logger.Errorf("Error reading from the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
// delete the tokens
if err := tx.Delete(user.Tokens).Error; err != nil {
logger.Errorf("Error deleting from the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
return nil
})
}
// Validate a token, each time a token is validated it extends its lifetime for another week
func (u *Users) Validate(ctx context.Context, req *pb.ValidateRequest, rsp *pb.ValidateResponse) error {
// validate the request
if len(req.Token) == 0 {
return ErrMissingToken
}
return u.DB.Transaction(func(tx *gorm.DB) error {
// lookup the token
var token Token
if err := tx.Where(&Token{Key: req.Token}).Preload("User").First(&token).Error; err == gorm.ErrRecordNotFound {
return ErrInvalidToken
} else if err != nil {
logger.Errorf("Error reading from the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
// ensure the token is valid
if u.Time().After(token.ExpiresAt) {
return ErrTokenExpired
}
// extend the token for another lifetime
token.ExpiresAt = u.Time().Add(tokenTTL)
if err := tx.Save(&token).Error; err != nil {
logger.Errorf("Error writing to the database: %v", err)
return errors.InternalServerError("DATABASE_ERROR", "Error connecting to the database")
}
// serialize the response
rsp.User = token.User.Serialize()
return nil
})
}
// isEmailValid checks if the email provided passes the required structure and length.
func isEmailValid(e string) bool {
if len(e) < 3 && len(e) > 254 {
return false
}
return emailRegex.MatchString(e)
}
func hashAndSalt(pwd string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hash), nil
}
func passwordsMatch(hashed string, plain string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashed), []byte(plain))
return err == nil
}

View File

@@ -0,0 +1,668 @@
package handler_test
import (
"context"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/micro/services/users/handler"
pb "github.com/micro/services/users/proto"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func testHandler(t *testing.T) *handler.Users {
// connect to the database
db, err := gorm.Open(postgres.Open("postgresql://postgres@localhost:5432/users?sslmode=disable"), &gorm.Config{})
if err != nil {
t.Fatalf("Error connecting to database: %v", err)
}
// migrate the database
if err := db.AutoMigrate(&handler.User{}, &handler.Token{}); err != nil {
t.Fatalf("Error migrating database: %v", err)
}
// clean any data from a previous run
if err := db.Exec("TRUNCATE TABLE users, tokens CASCADE").Error; err != nil {
t.Fatalf("Error cleaning database: %v", err)
}
return &handler.Users{DB: db, Time: time.Now}
}
func TestCreate(t *testing.T) {
tt := []struct {
Name string
FirstName string
LastName string
Email string
Password string
Error error
}{
{
Name: "MissingFirstName",
LastName: "Doe",
Email: "john@doe.com",
Password: "password",
Error: handler.ErrMissingFirstName,
},
{
Name: "MissingLastName",
FirstName: "John",
Email: "john@doe.com",
Password: "password",
Error: handler.ErrMissingLastName,
},
{
Name: "MissingEmail",
FirstName: "John",
LastName: "Doe",
Password: "password",
Error: handler.ErrMissingEmail,
},
{
Name: "InvalidEmail",
FirstName: "John",
LastName: "Doe",
Password: "password",
Email: "foo.foo.foo",
Error: handler.ErrInvalidEmail,
},
{
Name: "InvalidPassword",
FirstName: "John",
LastName: "Doe",
Email: "john@doe.com",
Password: "pwd",
Error: handler.ErrInvalidPassword,
},
}
// test the validations
h := testHandler(t)
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
err := h.Create(context.TODO(), &pb.CreateRequest{
FirstName: tc.FirstName,
LastName: tc.LastName,
Email: tc.Email,
Password: tc.Password,
}, &pb.CreateResponse{})
assert.Equal(t, tc.Error, err)
})
}
t.Run("Valid", func(t *testing.T) {
var rsp pb.CreateResponse
req := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "john@doe.com",
Password: "passwordabc",
}
err := h.Create(context.TODO(), &req, &rsp)
assert.NoError(t, err)
u := rsp.User
if u == nil {
t.Fatalf("No user returned")
}
assert.NotEmpty(t, u.Id)
assert.Equal(t, req.FirstName, u.FirstName)
assert.Equal(t, req.LastName, u.LastName)
assert.Equal(t, req.Email, u.Email)
assert.NotEmpty(t, rsp.Token)
})
t.Run("DuplicateEmail", func(t *testing.T) {
var rsp pb.CreateResponse
req := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "john@doe.com",
Password: "passwordabc",
}
err := h.Create(context.TODO(), &req, &rsp)
assert.Equal(t, handler.ErrDuplicateEmail, err)
assert.Nil(t, rsp.User)
})
t.Run("DifferentEmail", func(t *testing.T) {
var rsp pb.CreateResponse
req := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "johndoe@gmail.com",
Password: "passwordabc",
}
err := h.Create(context.TODO(), &req, &rsp)
assert.NoError(t, err)
u := rsp.User
if u == nil {
t.Fatalf("No user returned")
}
assert.NotEmpty(t, u.Id)
assert.Equal(t, req.FirstName, u.FirstName)
assert.Equal(t, req.LastName, u.LastName)
assert.Equal(t, req.Email, u.Email)
})
}
func TestRead(t *testing.T) {
h := testHandler(t)
t.Run("MissingIDs", func(t *testing.T) {
var rsp pb.ReadResponse
err := h.Read(context.TODO(), &pb.ReadRequest{}, &rsp)
assert.Equal(t, handler.ErrMissingIDs, err)
assert.Nil(t, rsp.Users)
})
t.Run("NotFound", func(t *testing.T) {
var rsp pb.ReadResponse
err := h.Read(context.TODO(), &pb.ReadRequest{Ids: []string{"foo"}}, &rsp)
assert.Nil(t, err)
if rsp.Users == nil {
t.Fatal("Expected the users object to not be nil")
}
assert.Nil(t, rsp.Users["foo"])
})
// create some mock data
var rsp1 pb.CreateResponse
req1 := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "john@doe.com",
Password: "passwordabc",
}
err := h.Create(context.TODO(), &req1, &rsp1)
assert.NoError(t, err)
if rsp1.User == nil {
t.Fatal("No user returned")
return
}
var rsp2 pb.CreateResponse
req2 := pb.CreateRequest{
FirstName: "Apple",
LastName: "Tree",
Email: "apple@tree.com",
Password: "passwordabc",
}
err = h.Create(context.TODO(), &req2, &rsp2)
assert.NoError(t, err)
if rsp2.User == nil {
t.Fatal("No user returned")
return
}
// test the read
var rsp pb.ReadResponse
err = h.Read(context.TODO(), &pb.ReadRequest{
Ids: []string{rsp1.User.Id, rsp2.User.Id},
}, &rsp)
assert.NoError(t, err)
if rsp.Users == nil {
t.Fatal("Users not returned")
return
}
assert.NotNil(t, rsp.Users[rsp1.User.Id])
assert.NotNil(t, rsp.Users[rsp2.User.Id])
// check the users match
if u := rsp.Users[rsp1.User.Id]; u != nil {
assert.Equal(t, rsp1.User.Id, u.Id)
assert.Equal(t, rsp1.User.FirstName, u.FirstName)
assert.Equal(t, rsp1.User.LastName, u.LastName)
assert.Equal(t, rsp1.User.Email, u.Email)
}
if u := rsp.Users[rsp2.User.Id]; u != nil {
assert.Equal(t, rsp2.User.Id, u.Id)
assert.Equal(t, rsp2.User.FirstName, u.FirstName)
assert.Equal(t, rsp2.User.LastName, u.LastName)
assert.Equal(t, rsp2.User.Email, u.Email)
}
}
func TestUpdate(t *testing.T) {
h := testHandler(t)
t.Run("MissingID", func(t *testing.T) {
var rsp pb.UpdateResponse
err := h.Update(context.TODO(), &pb.UpdateRequest{}, &rsp)
assert.Equal(t, handler.ErrMissingID, err)
assert.Nil(t, rsp.User)
})
t.Run("NotFound", func(t *testing.T) {
var rsp pb.UpdateResponse
err := h.Update(context.TODO(), &pb.UpdateRequest{Id: "foo"}, &rsp)
assert.Equal(t, handler.ErrNotFound, err)
assert.Nil(t, rsp.User)
})
// create some mock data
var cRsp1 pb.CreateResponse
cReq1 := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "john@doe.com",
Password: "passwordabc",
}
err := h.Create(context.TODO(), &cReq1, &cRsp1)
assert.NoError(t, err)
if cRsp1.User == nil {
t.Fatal("No user returned")
return
}
var cRsp2 pb.CreateResponse
cReq2 := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "johndoe@gmail.com",
Password: "passwordabc",
}
err = h.Create(context.TODO(), &cReq2, &cRsp2)
assert.NoError(t, err)
if cRsp2.User == nil {
t.Fatal("No user returned")
return
}
t.Run("BlankFirstName", func(t *testing.T) {
var rsp pb.UpdateResponse
err := h.Update(context.TODO(), &pb.UpdateRequest{
Id: cRsp1.User.Id, FirstName: &wrapperspb.StringValue{},
}, &rsp)
assert.Equal(t, handler.ErrMissingFirstName, err)
assert.Nil(t, rsp.User)
})
t.Run("BlankLastName", func(t *testing.T) {
var rsp pb.UpdateResponse
err := h.Update(context.TODO(), &pb.UpdateRequest{
Id: cRsp1.User.Id, LastName: &wrapperspb.StringValue{},
}, &rsp)
assert.Equal(t, handler.ErrMissingLastName, err)
assert.Nil(t, rsp.User)
})
t.Run("BlankLastName", func(t *testing.T) {
var rsp pb.UpdateResponse
err := h.Update(context.TODO(), &pb.UpdateRequest{
Id: cRsp1.User.Id, LastName: &wrapperspb.StringValue{},
}, &rsp)
assert.Equal(t, handler.ErrMissingLastName, err)
assert.Nil(t, rsp.User)
})
t.Run("BlankEmail", func(t *testing.T) {
var rsp pb.UpdateResponse
err := h.Update(context.TODO(), &pb.UpdateRequest{
Id: cRsp1.User.Id, Email: &wrapperspb.StringValue{},
}, &rsp)
assert.Equal(t, handler.ErrMissingEmail, err)
assert.Nil(t, rsp.User)
})
t.Run("InvalidEmail", func(t *testing.T) {
var rsp pb.UpdateResponse
err := h.Update(context.TODO(), &pb.UpdateRequest{
Id: cRsp1.User.Id, Email: &wrapperspb.StringValue{Value: "foo.bar"},
}, &rsp)
assert.Equal(t, handler.ErrInvalidEmail, err)
assert.Nil(t, rsp.User)
})
t.Run("EmailAlreadyExists", func(t *testing.T) {
var rsp pb.UpdateResponse
err := h.Update(context.TODO(), &pb.UpdateRequest{
Id: cRsp1.User.Id, Email: &wrapperspb.StringValue{Value: cRsp2.User.Email},
}, &rsp)
assert.Equal(t, handler.ErrDuplicateEmail, err)
assert.Nil(t, rsp.User)
})
t.Run("Valid", func(t *testing.T) {
uReq := pb.UpdateRequest{
Id: cRsp1.User.Id,
Email: &wrapperspb.StringValue{Value: "foobar@gmail.com"},
FirstName: &wrapperspb.StringValue{Value: "Foo"},
LastName: &wrapperspb.StringValue{Value: "Bar"},
}
var uRsp pb.UpdateResponse
err := h.Update(context.TODO(), &uReq, &uRsp)
assert.NoError(t, err)
if uRsp.User == nil {
t.Error("No user returned")
return
}
assert.Equal(t, cRsp1.User.Id, uRsp.User.Id)
assert.Equal(t, uReq.Email.Value, uRsp.User.Email)
assert.Equal(t, uReq.FirstName.Value, uRsp.User.FirstName)
assert.Equal(t, uReq.LastName.Value, uRsp.User.LastName)
})
}
func TestDelete(t *testing.T) {
h := testHandler(t)
t.Run("MissingID", func(t *testing.T) {
err := h.Delete(context.TODO(), &pb.DeleteRequest{}, &pb.DeleteResponse{})
assert.Equal(t, handler.ErrMissingID, err)
})
// create some mock data
var cRsp pb.CreateResponse
cReq := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "john@doe.com",
Password: "passwordabc",
}
err := h.Create(context.TODO(), &cReq, &cRsp)
assert.NoError(t, err)
if cRsp.User == nil {
t.Fatal("No user returned")
return
}
t.Run("Valid", func(t *testing.T) {
err := h.Delete(context.TODO(), &pb.DeleteRequest{
Id: cRsp.User.Id,
}, &pb.DeleteResponse{})
assert.NoError(t, err)
// check it was actually deleted
var rsp pb.ReadResponse
err = h.Read(context.TODO(), &pb.ReadRequest{
Ids: []string{cRsp.User.Id},
}, &rsp)
assert.NoError(t, err)
assert.Nil(t, rsp.Users[cRsp.User.Id])
})
t.Run("Retry", func(t *testing.T) {
err := h.Delete(context.TODO(), &pb.DeleteRequest{
Id: cRsp.User.Id,
}, &pb.DeleteResponse{})
assert.NoError(t, err)
})
}
func TestList(t *testing.T) {
h := testHandler(t)
// create some mock data
var cRsp1 pb.CreateResponse
cReq1 := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "john@doe.com",
Password: "passwordabc",
}
err := h.Create(context.TODO(), &cReq1, &cRsp1)
assert.NoError(t, err)
if cRsp1.User == nil {
t.Fatal("No user returned")
return
}
var cRsp2 pb.CreateResponse
cReq2 := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "johndoe@gmail.com",
Password: "passwordabc",
}
err = h.Create(context.TODO(), &cReq2, &cRsp2)
assert.NoError(t, err)
if cRsp2.User == nil {
t.Fatal("No user returned")
return
}
var rsp pb.ListResponse
err = h.List(context.TODO(), &pb.ListRequest{}, &rsp)
assert.NoError(t, err)
if rsp.Users == nil {
t.Error("No users returned")
return
}
var u1Found, u2Found bool
for _, u := range rsp.Users {
switch u.Id {
case cRsp1.User.Id:
assertUsersMatch(t, cRsp1.User, u)
u1Found = true
case cRsp2.User.Id:
assertUsersMatch(t, cRsp2.User, u)
u2Found = true
default:
t.Fatal("Unexpected user returned")
return
}
}
assert.True(t, u1Found)
assert.True(t, u2Found)
}
func TestLogin(t *testing.T) {
h := testHandler(t)
// create some mock data
var cRsp pb.CreateResponse
cReq := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "john@doe.com",
Password: "passwordabc",
}
err := h.Create(context.TODO(), &cReq, &cRsp)
assert.NoError(t, err)
if cRsp.User == nil {
t.Fatal("No user returned")
return
}
tt := []struct {
Name string
Email string
Password string
Error error
User *pb.User
}{
{
Name: "MissingEmail",
Password: "passwordabc",
Error: handler.ErrMissingEmail,
},
{
Name: "MissingPassword",
Email: "john@doe.com",
Error: handler.ErrInvalidPassword,
},
{
Name: "UserNotFound",
Email: "foo@bar.com",
Password: "passwordabc",
Error: handler.ErrNotFound,
},
{
Name: "IncorrectPassword",
Email: "john@doe.com",
Password: "passwordabcdef",
Error: handler.ErrIncorrectPassword,
},
{
Name: "Valid",
Email: "john@doe.com",
Password: "passwordabc",
User: cRsp.User,
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
var rsp pb.LoginResponse
err := h.Login(context.TODO(), &pb.LoginRequest{
Email: tc.Email, Password: tc.Password,
}, &rsp)
assert.Equal(t, tc.Error, err)
if tc.User != nil {
assertUsersMatch(t, tc.User, rsp.User)
assert.NotEmpty(t, rsp.Token)
} else {
assert.Nil(t, tc.User)
}
})
}
}
func TestLogout(t *testing.T) {
h := testHandler(t)
t.Run("MissingUserID", func(t *testing.T) {
err := h.Logout(context.TODO(), &pb.LogoutRequest{}, &pb.LogoutResponse{})
assert.Equal(t, handler.ErrMissingID, err)
})
t.Run("UserNotFound", func(t *testing.T) {
err := h.Logout(context.TODO(), &pb.LogoutRequest{Id: uuid.New().String()}, &pb.LogoutResponse{})
assert.Equal(t, handler.ErrNotFound, err)
})
t.Run("Valid", func(t *testing.T) {
// create some mock data
var cRsp pb.CreateResponse
cReq := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "john@doe.com",
Password: "passwordabc",
}
err := h.Create(context.TODO(), &cReq, &cRsp)
assert.NoError(t, err)
if cRsp.User == nil {
t.Fatal("No user returned")
return
}
err = h.Logout(context.TODO(), &pb.LogoutRequest{Id: cRsp.User.Id}, &pb.LogoutResponse{})
assert.NoError(t, err)
err = h.Validate(context.TODO(), &pb.ValidateRequest{Token: cRsp.Token}, &pb.ValidateResponse{})
assert.Error(t, err)
})
}
func TestValidate(t *testing.T) {
h := testHandler(t)
// create some mock data
var cRsp1 pb.CreateResponse
cReq1 := pb.CreateRequest{
FirstName: "John",
LastName: "Doe",
Email: "john@doe.com",
Password: "passwordabc",
}
err := h.Create(context.TODO(), &cReq1, &cRsp1)
assert.NoError(t, err)
if cRsp1.User == nil {
t.Fatal("No user returned")
return
}
var cRsp2 pb.CreateResponse
cReq2 := pb.CreateRequest{
FirstName: "Barry",
LastName: "Doe",
Email: "barry@doe.com",
Password: "passwordabc",
}
err = h.Create(context.TODO(), &cReq2, &cRsp2)
assert.NoError(t, err)
if cRsp2.User == nil {
t.Fatal("No user returned")
return
}
tt := []struct {
Name string
Token string
Time func() time.Time
Error error
User *pb.User
}{
{
Name: "MissingToken",
Error: handler.ErrMissingToken,
},
{
Name: "InvalidToken",
Error: handler.ErrInvalidToken,
Token: uuid.New().String(),
},
{
Name: "ExpiredToken",
Error: handler.ErrTokenExpired,
Token: cRsp1.Token,
Time: func() time.Time { return time.Now().Add(time.Hour * 24 * 8) },
},
{
Name: "ValidToken",
User: cRsp2.User,
Token: cRsp2.Token,
Time: func() time.Time { return time.Now().Add(time.Hour * 24 * 3) },
},
{
Name: "RefreshedToken",
User: cRsp2.User,
Token: cRsp2.Token,
Time: func() time.Time { return time.Now().Add(time.Hour * 24 * 8) },
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
if tc.Time == nil {
h.Time = time.Now
} else {
h.Time = tc.Time
}
var rsp pb.ValidateResponse
err := h.Validate(context.TODO(), &pb.ValidateRequest{Token: tc.Token}, &rsp)
assert.Equal(t, tc.Error, err)
if tc.User != nil {
assertUsersMatch(t, tc.User, rsp.User)
} else {
assert.Nil(t, tc.User)
}
})
}
}
func assertUsersMatch(t *testing.T, exp, act *pb.User) {
if act == nil {
t.Error("No user returned")
return
}
assert.Equal(t, exp.Id, act.Id)
assert.Equal(t, exp.FirstName, act.FirstName)
assert.Equal(t, exp.LastName, act.LastName)
assert.Equal(t, exp.Email, act.Email)
}

View File

@@ -1,22 +1,43 @@
package main
import (
"github.com/micro/micro/v3/service"
"github.com/micro/micro/v3/service/logger"
"time"
"github.com/micro/services/users/handler"
proto "github.com/micro/services/users/proto"
pb "github.com/micro/services/users/proto"
"github.com/micro/micro/v3/service"
"github.com/micro/micro/v3/service/config"
"github.com/micro/micro/v3/service/logger"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var dbAddress = "postgresql://postgres@localhost:5432/users?sslmode=disable"
func main() {
service := service.New(
// Create service
srv := service.New(
service.Name("users"),
service.Version("latest"),
)
service.Init()
// Connect to the database
cfg, err := config.Get("users.database")
if err != nil {
logger.Fatalf("Error loading config: %v", err)
}
addr := cfg.String(dbAddress)
db, err := gorm.Open(postgres.Open(addr), &gorm.Config{})
if err != nil {
logger.Fatalf("Error connecting to database: %v", err)
}
proto.RegisterUsersHandler(service.Server(), handler.NewUsers())
// Register handler
pb.RegisterUsersHandler(srv.Server(), &handler.Users{DB: db, Time: time.Now})
if err := service.Run(); err != nil {
// Run service
if err := srv.Run(); err != nil {
logger.Fatal(err)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@ package users
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
_ "github.com/golang/protobuf/ptypes/wrappers"
math "math"
)
@@ -46,11 +47,13 @@ type UsersService interface {
Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error)
Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error)
Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error)
Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error)
UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, opts ...client.CallOption) (*UpdatePasswordResponse, error)
List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error)
// Login using email and password returns the users profile and a token
Login(ctx context.Context, in *LoginRequest, opts ...client.CallOption) (*LoginResponse, error)
// Logout expires all tokens for the user
Logout(ctx context.Context, in *LogoutRequest, opts ...client.CallOption) (*LogoutResponse, error)
ReadSession(ctx context.Context, in *ReadSessionRequest, opts ...client.CallOption) (*ReadSessionResponse, error)
// Validate a token, each time a token is validated it extends its lifetime for another week
Validate(ctx context.Context, in *ValidateRequest, opts ...client.CallOption) (*ValidateResponse, error)
}
type usersService struct {
@@ -105,19 +108,9 @@ func (c *usersService) Delete(ctx context.Context, in *DeleteRequest, opts ...cl
return out, nil
}
func (c *usersService) Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error) {
req := c.c.NewRequest(c.name, "Users.Search", in)
out := new(SearchResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *usersService) UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, opts ...client.CallOption) (*UpdatePasswordResponse, error) {
req := c.c.NewRequest(c.name, "Users.UpdatePassword", in)
out := new(UpdatePasswordResponse)
func (c *usersService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) {
req := c.c.NewRequest(c.name, "Users.List", in)
out := new(ListResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
@@ -145,9 +138,9 @@ func (c *usersService) Logout(ctx context.Context, in *LogoutRequest, opts ...cl
return out, nil
}
func (c *usersService) ReadSession(ctx context.Context, in *ReadSessionRequest, opts ...client.CallOption) (*ReadSessionResponse, error) {
req := c.c.NewRequest(c.name, "Users.ReadSession", in)
out := new(ReadSessionResponse)
func (c *usersService) Validate(ctx context.Context, in *ValidateRequest, opts ...client.CallOption) (*ValidateResponse, error) {
req := c.c.NewRequest(c.name, "Users.Validate", in)
out := new(ValidateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
@@ -162,11 +155,13 @@ type UsersHandler interface {
Read(context.Context, *ReadRequest, *ReadResponse) error
Update(context.Context, *UpdateRequest, *UpdateResponse) error
Delete(context.Context, *DeleteRequest, *DeleteResponse) error
Search(context.Context, *SearchRequest, *SearchResponse) error
UpdatePassword(context.Context, *UpdatePasswordRequest, *UpdatePasswordResponse) error
List(context.Context, *ListRequest, *ListResponse) error
// Login using email and password returns the users profile and a token
Login(context.Context, *LoginRequest, *LoginResponse) error
// Logout expires all tokens for the user
Logout(context.Context, *LogoutRequest, *LogoutResponse) error
ReadSession(context.Context, *ReadSessionRequest, *ReadSessionResponse) error
// Validate a token, each time a token is validated it extends its lifetime for another week
Validate(context.Context, *ValidateRequest, *ValidateResponse) error
}
func RegisterUsersHandler(s server.Server, hdlr UsersHandler, opts ...server.HandlerOption) error {
@@ -175,11 +170,10 @@ func RegisterUsersHandler(s server.Server, hdlr UsersHandler, opts ...server.Han
Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error
Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error
Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error
Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error
UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, out *UpdatePasswordResponse) error
List(ctx context.Context, in *ListRequest, out *ListResponse) error
Login(ctx context.Context, in *LoginRequest, out *LoginResponse) error
Logout(ctx context.Context, in *LogoutRequest, out *LogoutResponse) error
ReadSession(ctx context.Context, in *ReadSessionRequest, out *ReadSessionResponse) error
Validate(ctx context.Context, in *ValidateRequest, out *ValidateResponse) error
}
type Users struct {
users
@@ -208,12 +202,8 @@ func (h *usersHandler) Delete(ctx context.Context, in *DeleteRequest, out *Delet
return h.UsersHandler.Delete(ctx, in, out)
}
func (h *usersHandler) Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error {
return h.UsersHandler.Search(ctx, in, out)
}
func (h *usersHandler) UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, out *UpdatePasswordResponse) error {
return h.UsersHandler.UpdatePassword(ctx, in, out)
func (h *usersHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error {
return h.UsersHandler.List(ctx, in, out)
}
func (h *usersHandler) Login(ctx context.Context, in *LoginRequest, out *LoginResponse) error {
@@ -224,6 +214,6 @@ func (h *usersHandler) Logout(ctx context.Context, in *LogoutRequest, out *Logou
return h.UsersHandler.Logout(ctx, in, out)
}
func (h *usersHandler) ReadSession(ctx context.Context, in *ReadSessionRequest, out *ReadSessionResponse) error {
return h.UsersHandler.ReadSession(ctx, in, out)
func (h *usersHandler) Validate(ctx context.Context, in *ValidateRequest, out *ValidateResponse) error {
return h.UsersHandler.Validate(ctx, in, out)
}

View File

@@ -2,112 +2,93 @@ syntax = "proto3";
package users;
option go_package = "proto;users";
import "google/protobuf/wrappers.proto";
service Users {
rpc Create(CreateRequest) returns (CreateResponse) {}
rpc Read(ReadRequest) returns (ReadResponse) {}
rpc Update(UpdateRequest) returns (UpdateResponse) {}
rpc Delete(DeleteRequest) returns (DeleteResponse) {}
rpc Search(SearchRequest) returns (SearchResponse) {}
rpc UpdatePassword(UpdatePasswordRequest) returns (UpdatePasswordResponse) {}
rpc List(ListRequest) returns (ListResponse) {}
// Login using email and password returns the users profile and a token
rpc Login(LoginRequest) returns (LoginResponse) {}
// Logout expires all tokens for the user
rpc Logout(LogoutRequest) returns (LogoutResponse) {}
rpc ReadSession(ReadSessionRequest) returns(ReadSessionResponse) {}
// Validate a token, each time a token is validated it extends its lifetime for another week
rpc Validate(ValidateRequest) returns (ValidateResponse) {}
}
message User {
string id = 1; // uuid
string username = 2; // alphanumeric user or org
string email = 3;
int64 created = 4; // unix
int64 updated = 5; // unix
}
message Session {
string id = 1;
string username = 2;
string email = 3;
int64 created = 4; // unix
int64 expires = 5; // unix
string id = 1;
string first_name = 2;
string last_name = 3;
string email = 4;
}
message CreateRequest {
string id = 1; // uuid
string username = 2; // alphanumeric user or org
string first_name = 1;
string last_name = 2;
string email = 3;
string password = 4;
string password = 4;
}
message CreateResponse {
User user = 1;
string token = 2;
}
message ReadRequest {
repeated string ids = 1;
}
message ReadResponse {
map<string,User> users = 1;
}
message UpdateRequest {
string id = 1;
google.protobuf.StringValue first_name = 2;
google.protobuf.StringValue last_name = 3;
google.protobuf.StringValue email = 4;
}
message UpdateResponse {
User user = 1;
}
message DeleteRequest {
string id = 1;
}
message DeleteResponse {
}
message DeleteResponse {}
message ReadRequest {
string id = 1;
}
message ListRequest {}
message ReadResponse {
User user = 1;
}
message UpdateRequest {
string id = 1; // uuid
string username = 2; // alphanumeric user or org
string email = 3;
}
message UpdateResponse {
}
message UpdatePasswordRequest {
string userId = 1;
string oldPassword = 2;
string newPassword = 3;
string confirm_password = 4;
}
message UpdatePasswordResponse {
}
message SearchRequest {
string username = 1;
string email = 2;
int64 limit = 3;
int64 offset = 4;
}
message SearchResponse {
message ListResponse {
repeated User users = 1;
}
message ReadSessionRequest {
string sessionId = 1;
}
message ReadSessionResponse {
Session session = 1;
}
message LoginRequest {
string username = 1;
string email = 2;
string password = 3;
string email = 1;
string password = 2;
}
message LoginResponse {
Session session = 1;
User user = 1;
string token = 2;
}
message LogoutRequest {
string sessionId = 1;
string id = 1;
}
message LogoutResponse {
message LogoutResponse {}
message ValidateRequest {
string token = 1;
}
message ValidateResponse {
User user = 1;
}