diff --git a/user/domain/domain.go b/user/domain/domain.go index 5370cdd..9d55bdf 100644 --- a/user/domain/domain.go +++ b/user/domain/domain.go @@ -6,13 +6,16 @@ import ( "errors" "fmt" "net/url" + "path" "strings" "time" _struct "github.com/golang/protobuf/ptypes/struct" "github.com/micro/micro/v3/service/config" + microerr "github.com/micro/micro/v3/service/errors" "github.com/micro/micro/v3/service/logger" db "github.com/micro/services/db/proto" + "github.com/micro/services/pkg/cache" user "github.com/micro/services/user/proto" "github.com/sendgrid/sendgrid-go" @@ -463,3 +466,57 @@ func (domain *Domain) List(ctx context.Context, o, l int32) ([]*user.Account, er } return ret, nil } + +func (domain *Domain) CacheToken(ctx context.Context, token, id, email string, ttl int) error { + obj := &tokenObject{ + Id: id, + Email: email, + } + + expires := time.Now().Add(time.Duration(ttl) * time.Second) + + err := cache.Context(ctx).Set(token, obj, expires) + + return err +} + +func (domain *Domain) SendMLE(fromName, toAddress, toUsername, subject, textContent, token, address, endpoint string) error { + if domain.sengridKey == "" { + return fmt.Errorf("empty email api key") + } + from := mail.NewEmail(fromName, "support@m3o.com") + to := mail.NewEmail(toUsername, toAddress) + textContent = strings.Replace(textContent, "$micro_verification_link", fmt.Sprint("https://", path.Join(address, endpoint, token)), -1) + message := mail.NewSingleEmail(from, subject, to, textContent, "") + client := sendgrid.NewSendClient(domain.sengridKey) + response, err := client.Send(message) + logger.Info(response) + + return err +} + +func (domain *Domain) CacheReadToken(ctx context.Context, token string) (string, string, error) { + + if token == "" { + return "", "", errors.New("token empty") + } + + var obj tokenObject + + expires, err := cache.Context(ctx).Get(token, obj) + + if err == cache.ErrNotFound { + return "", "", errors.New("token not found") + } else if time.Until(expires).Seconds() < 0 { + return "", "", errors.New("token expired") + } else if err != nil { + return "", "", microerr.InternalServerError("CacheReadToken", err.Error()) + } + + return obj.Id, obj.Email, nil +} + +type tokenObject struct { + Id string + Email string +} diff --git a/user/examples.json b/user/examples.json index 6ee09cd..f26687c 100644 --- a/user/examples.json +++ b/user/examples.json @@ -226,5 +226,40 @@ ] } } + ], + "sendMagicLink": [ + { + "title": "Send a MagicLink", + "run_check": false, + "request": { + "email": "joe@example.com", + "subject": "MagicLink to access your account", + "textContent": "Hi there,\n\nClick here to access your account $micro_verification_link", + "fromName": "Awesome Dot Com", + "address": "www.example.com", + "endpoint": "verifytoken" + }, + "response": { + "session": { + "id": "df91a612-5b24-4634-99ff-240220ab8f55", + "created": "1623677579", + "expires": "1623699579", + "userId": "8b98acbe-0b6a-4d66-a414-5ffbf666786f" + } + } + } + ], + "verifyToken": [ + { + "title": "Verify a Token", + "run_check": false, + "request": { + "token": "EdsUiidouJJJLldjlloofUiorkojflsWWdld" + }, + "response": { + "is_valid": false, + "message": "" + } + } ] } diff --git a/user/handler/handler.go b/user/handler/handler.go index e93b82b..82d8201 100644 --- a/user/handler/handler.go +++ b/user/handler/handler.go @@ -4,15 +4,20 @@ import ( goctx "context" "crypto/rand" "encoding/base64" + "encoding/json" "fmt" + "path" "regexp" "strings" "time" + "github.com/asim/mq/broker" "github.com/google/uuid" "github.com/micro/micro/v3/service/errors" + "github.com/micro/micro/v3/service/logger" db "github.com/micro/services/db/proto" otp "github.com/micro/services/otp/proto" + "github.com/micro/services/pkg/tenant" "github.com/micro/services/user/domain" pb "github.com/micro/services/user/proto" "golang.org/x/crypto/bcrypt" @@ -257,6 +262,10 @@ func (s *User) VerifyEmail(ctx context.Context, req *pb.VerifyEmailRequest, rsp // mark user as verified user, err := s.domain.Read(ctx, userId) + if err != nil { + return err + } + user.Verified = true // update the user @@ -391,3 +400,145 @@ func (s *User) List(ctx goctx.Context, request *pb.ListRequest, response *pb.Lis } return nil } + +func (s *User) SendMagicLink(ctx context.Context, req *pb.SendMagicLinkRequest, stream pb.User_SendMagicLinkStream) error { + // check if the email has the correct format + if !emailFormat.MatchString(req.Email) { + return errors.BadRequest("SendMagicLink.email-format-check", "email has wrong format") + } + + // check if the email exist in the DB + users, err := s.domain.Search(ctx, "", req.Email) + if err.Error() == "not found" { + return errors.BadRequest("SendMagicLink.email-check", "email doesn't exist") + } else if err != nil { + return errors.BadRequest("SendMagicLink.email-check", err.Error()) + } + + // create a token object + token := random(128) + + // set ttl to 60 seconds + ttl := 60 + + // uuid part of the topic + topic := uuid.New().String() + + // save token, so we can retrieve it later + err = s.domain.CacheToken(ctx, token, topic, req.Email, ttl) + if err != nil { + return errors.BadRequest("SendMagicLink.cacheToken", "Oooops something went wrong") + } + + // send magic link to email address + err = s.domain.SendMLE(req.FromName, req.Email, users[0].Username, req.Subject, req.TextContent, token, req.Address, req.Endpoint) + if err != nil { + return errors.BadRequest("SendMagicLink.sendEmail", "Oooops something went wrong") + } + + // subscribe to the topic + id, ok := tenant.FromContext(ctx) + if !ok { + id = "default" + } + + // create tenant based topics + topic = path.Join("stream", id, topic) + + logger.Infof("Tenant %v subscribing to %v\n", id, topic) + + sub, err := broker.Subscribe(topic) + if err != nil { + return errors.InternalServerError("SendMagicLink.subscribe", "failed to subscribe to topic") + } + defer broker.Unsubscribe(topic, sub) + + // range over the messages until the subscriber is closed + for msg := range sub { + // unmarshal the message into a struct + s := &pb.Session{} + err = json.Unmarshal(msg, s) + if err != nil { + return errors.InternalServerError("SendMgicLink.unmarshal", "faild to unmarshal the message") + } + + if err := stream.Send(&pb.SendMagicLinkResponse{ + Session: s, + }); err != nil { + return err + } + } + + return nil +} + +func (s *User) VerifyToken(ctx context.Context, req *pb.VerifyTokenRequest, rsp *pb.VerifyTokenResponse) error { + // extract token + token := req.Token + + // check if token is valid + topic, email, err := s.domain.CacheReadToken(ctx, token) + if err.Error() == "token not found" { + rsp.IsValid = false + rsp.Message = err.Error() + return nil + } else if err.Error() == "token expired" { + rsp.IsValid = false + rsp.Message = err.Error() + return nil + } else if err.Error() == "token empty" { + rsp.IsValid = false + rsp.Message = err.Error() + return nil + } else if err != nil { + return errors.BadRequest("VerifyToken.CacheReadToken", err.Error()) + } + + // save session + accounts, err := s.domain.Search(ctx, "", email) + if err != nil { + return err + } + if len(accounts) == 0 { + rsp.IsValid = false + rsp.Message = "account not found" + return nil + } + + sess := &pb.Session{ + Id: random(128), + Created: time.Now().Unix(), + Expires: time.Now().Add(time.Hour * 24 * 7).Unix(), + UserId: accounts[0].Id, + } + + if err := s.domain.CreateSession(ctx, sess); err != nil { + return errors.InternalServerError("VerifyToken.createSession", err.Error()) + } + + // publish a message which holds the session value. + // get the tenant + id, ok := tenant.FromContext(ctx) + if !ok { + id = "default" + } + + // create tenant based topics + topic = path.Join("stream", id, topic) + + // marshal the data + b, _ := json.Marshal(sess) + + logger.Infof("Tenant %v publishing to %v\n", id, topic) + + // publish the message + err = broker.Publish(topic, b) + if err != nil { + return errors.InternalServerError("VerifyToken.publish", "Ooops something went wrong, please try again") + } + + rsp.IsValid = true + rsp.Message = "" + + return nil +} diff --git a/user/proto/user.pb.go b/user/proto/user.pb.go index 7eb751c..ea6a26b 100644 --- a/user/proto/user.pb.go +++ b/user/proto/user.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.26.0 -// protoc v3.15.5 +// protoc-gen-go v1.27.1 +// protoc v3.18.1 // source: proto/user.proto package user @@ -1523,7 +1523,7 @@ func (*ResetPasswordResponse) Descriptor() ([]byte, []int) { return file_proto_user_proto_rawDescGZIP(), []int{25} } -// List all users +// List all users. Returns a paged list of results type ListRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1628,6 +1628,255 @@ func (x *ListResponse) GetUsers() []*Account { return nil } +// Login using email only - Passwordless +type SendMagicLinkRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // the email address of the user + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + // Text content of the email. Don't forget to include the string '$micro_verification_link' which will be replaced by the real verification link + // HTML emails are not available currently. + TextContent string `protobuf:"bytes,3,opt,name=textContent,proto3" json:"textContent,omitempty"` + // Display name of the sender for the email. Note: the email address will still be 'support@m3o.com' + FromName string `protobuf:"bytes,4,opt,name=fromName,proto3" json:"fromName,omitempty"` + // Your web site address, example www.example.com or user.example.com + Address string `protobuf:"bytes,5,opt,name=address,proto3" json:"address,omitempty"` + // Endpoint name where your http request handler handles MagicLink by + // calling M3O VerifyToken endpoint. You can return as a result a success, + // failed or redirect to another page. + Endpoint string `protobuf:"bytes,6,opt,name=endpoint,proto3" json:"endpoint,omitempty"` +} + +func (x *SendMagicLinkRequest) Reset() { + *x = SendMagicLinkRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_user_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendMagicLinkRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendMagicLinkRequest) ProtoMessage() {} + +func (x *SendMagicLinkRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendMagicLinkRequest.ProtoReflect.Descriptor instead. +func (*SendMagicLinkRequest) Descriptor() ([]byte, []int) { + return file_proto_user_proto_rawDescGZIP(), []int{28} +} + +func (x *SendMagicLinkRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *SendMagicLinkRequest) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *SendMagicLinkRequest) GetTextContent() string { + if x != nil { + return x.TextContent + } + return "" +} + +func (x *SendMagicLinkRequest) GetFromName() string { + if x != nil { + return x.FromName + } + return "" +} + +func (x *SendMagicLinkRequest) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *SendMagicLinkRequest) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + +type SendMagicLinkResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Session *Session `protobuf:"bytes,1,opt,name=session,proto3" json:"session,omitempty"` +} + +func (x *SendMagicLinkResponse) Reset() { + *x = SendMagicLinkResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_user_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendMagicLinkResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendMagicLinkResponse) ProtoMessage() {} + +func (x *SendMagicLinkResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendMagicLinkResponse.ProtoReflect.Descriptor instead. +func (*SendMagicLinkResponse) Descriptor() ([]byte, []int) { + return file_proto_user_proto_rawDescGZIP(), []int{29} +} + +func (x *SendMagicLinkResponse) GetSession() *Session { + if x != nil { + return x.Session + } + return nil +} + +// Check whether the token attached to MagicLink is valid or not. +// Ideally, you need to call this endpoint from your http request +// handler that handles the endpoint which is specified in the +// SendMagicLink request. +type VerifyTokenRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` +} + +func (x *VerifyTokenRequest) Reset() { + *x = VerifyTokenRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_user_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VerifyTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VerifyTokenRequest) ProtoMessage() {} + +func (x *VerifyTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VerifyTokenRequest.ProtoReflect.Descriptor instead. +func (*VerifyTokenRequest) Descriptor() ([]byte, []int) { + return file_proto_user_proto_rawDescGZIP(), []int{30} +} + +func (x *VerifyTokenRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +type VerifyTokenResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IsValid bool `protobuf:"varint,1,opt,name=is_valid,json=isValid,proto3" json:"is_valid,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *VerifyTokenResponse) Reset() { + *x = VerifyTokenResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_user_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VerifyTokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VerifyTokenResponse) ProtoMessage() {} + +func (x *VerifyTokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VerifyTokenResponse.ProtoReflect.Descriptor instead. +func (*VerifyTokenResponse) Descriptor() ([]byte, []int) { + return file_proto_user_proto_rawDescGZIP(), []int{31} +} + +func (x *VerifyTokenResponse) GetIsValid() bool { + if x != nil { + return x.IsValid + } + return false +} + +func (x *VerifyTokenResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + var File_proto_user_proto protoreflect.FileDescriptor var file_proto_user_proto_rawDesc = []byte{ @@ -1785,63 +2034,96 @@ var file_proto_user_proto_rawDesc = []byte{ 0x6d, 0x69, 0x74, 0x22, 0x33, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x32, 0xea, 0x06, 0x0a, 0x04, 0x55, 0x73, 0x65, - 0x72, 0x12, 0x35, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x14, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, - 0x12, 0x11, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x06, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x35, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x14, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1b, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x32, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, - 0x12, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x06, 0x4c, 0x6f, - 0x67, 0x6f, 0x75, 0x74, 0x12, 0x13, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, - 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x44, 0x0a, 0x0b, 0x52, 0x65, 0x61, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x18, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x69, 0x66, - 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x18, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x56, 0x65, - 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x19, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, - 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, - 0x15, 0x53, 0x65, 0x6e, 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x22, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6d, - 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x65, 0x0a, 0x16, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x52, 0x65, 0x73, 0x65, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, - 0x65, 0x73, 0x65, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x24, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x65, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, - 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, - 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x12, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x3b, 0x75, 0x73, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x14, 0x53, 0x65, 0x6e, + 0x64, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x65, 0x78, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x65, 0x78, 0x74, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x40, 0x0a, 0x15, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x67, + 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, + 0x0a, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0d, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2a, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x22, 0x4a, 0x0a, 0x13, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, + 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, + 0xfe, 0x07, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x12, 0x13, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x2f, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x11, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x52, + 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x35, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x14, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x12, 0x13, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4d, + 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x12, 0x1b, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x32, 0x0a, + 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x35, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x13, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x14, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0b, 0x52, 0x65, 0x61, 0x64, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x52, + 0x65, 0x61, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, + 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x18, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, 0x15, 0x53, 0x65, 0x6e, 0x64, 0x56, 0x65, 0x72, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x22, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x23, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x16, 0x53, 0x65, 0x6e, 0x64, + 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x65, 0x74, 0x45, 0x6d, 0x61, + 0x69, 0x6c, 0x12, 0x23, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x65, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x65, 0x74, + 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x4a, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x12, 0x1a, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x04, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0d, + 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x1a, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x4c, 0x69, + 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x75, 0x73, 0x65, 0x72, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1856,7 +2138,7 @@ func file_proto_user_proto_rawDescGZIP() []byte { return file_proto_user_proto_rawDescData } -var file_proto_user_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_proto_user_proto_msgTypes = make([]protoimpl.MessageInfo, 35) var file_proto_user_proto_goTypes = []interface{}{ (*Account)(nil), // 0: user.Account (*Session)(nil), // 1: user.Session @@ -1886,50 +2168,59 @@ var file_proto_user_proto_goTypes = []interface{}{ (*ResetPasswordResponse)(nil), // 25: user.ResetPasswordResponse (*ListRequest)(nil), // 26: user.ListRequest (*ListResponse)(nil), // 27: user.ListResponse - nil, // 28: user.Account.ProfileEntry - nil, // 29: user.CreateRequest.ProfileEntry - nil, // 30: user.UpdateRequest.ProfileEntry + (*SendMagicLinkRequest)(nil), // 28: user.SendMagicLinkRequest + (*SendMagicLinkResponse)(nil), // 29: user.SendMagicLinkResponse + (*VerifyTokenRequest)(nil), // 30: user.VerifyTokenRequest + (*VerifyTokenResponse)(nil), // 31: user.VerifyTokenResponse + nil, // 32: user.Account.ProfileEntry + nil, // 33: user.CreateRequest.ProfileEntry + nil, // 34: user.UpdateRequest.ProfileEntry } var file_proto_user_proto_depIdxs = []int32{ - 28, // 0: user.Account.profile:type_name -> user.Account.ProfileEntry - 29, // 1: user.CreateRequest.profile:type_name -> user.CreateRequest.ProfileEntry + 32, // 0: user.Account.profile:type_name -> user.Account.ProfileEntry + 33, // 1: user.CreateRequest.profile:type_name -> user.CreateRequest.ProfileEntry 0, // 2: user.CreateResponse.account:type_name -> user.Account 0, // 3: user.ReadResponse.account:type_name -> user.Account - 30, // 4: user.UpdateRequest.profile:type_name -> user.UpdateRequest.ProfileEntry + 34, // 4: user.UpdateRequest.profile:type_name -> user.UpdateRequest.ProfileEntry 1, // 5: user.ReadSessionResponse.session:type_name -> user.Session 1, // 6: user.LoginResponse.session:type_name -> user.Session 0, // 7: user.ListResponse.users:type_name -> user.Account - 2, // 8: user.User.Create:input_type -> user.CreateRequest - 6, // 9: user.User.Read:input_type -> user.ReadRequest - 8, // 10: user.User.Update:input_type -> user.UpdateRequest - 4, // 11: user.User.Delete:input_type -> user.DeleteRequest - 10, // 12: user.User.UpdatePassword:input_type -> user.UpdatePasswordRequest - 14, // 13: user.User.Login:input_type -> user.LoginRequest - 16, // 14: user.User.Logout:input_type -> user.LogoutRequest - 12, // 15: user.User.ReadSession:input_type -> user.ReadSessionRequest - 18, // 16: user.User.VerifyEmail:input_type -> user.VerifyEmailRequest - 20, // 17: user.User.SendVerificationEmail:input_type -> user.SendVerificationEmailRequest - 22, // 18: user.User.SendPasswordResetEmail:input_type -> user.SendPasswordResetEmailRequest - 24, // 19: user.User.ResetPassword:input_type -> user.ResetPasswordRequest - 26, // 20: user.User.List:input_type -> user.ListRequest - 3, // 21: user.User.Create:output_type -> user.CreateResponse - 7, // 22: user.User.Read:output_type -> user.ReadResponse - 9, // 23: user.User.Update:output_type -> user.UpdateResponse - 5, // 24: user.User.Delete:output_type -> user.DeleteResponse - 11, // 25: user.User.UpdatePassword:output_type -> user.UpdatePasswordResponse - 15, // 26: user.User.Login:output_type -> user.LoginResponse - 17, // 27: user.User.Logout:output_type -> user.LogoutResponse - 13, // 28: user.User.ReadSession:output_type -> user.ReadSessionResponse - 19, // 29: user.User.VerifyEmail:output_type -> user.VerifyEmailResponse - 21, // 30: user.User.SendVerificationEmail:output_type -> user.SendVerificationEmailResponse - 23, // 31: user.User.SendPasswordResetEmail:output_type -> user.SendPasswordResetEmailResponse - 25, // 32: user.User.ResetPassword:output_type -> user.ResetPasswordResponse - 27, // 33: user.User.List:output_type -> user.ListResponse - 21, // [21:34] is the sub-list for method output_type - 8, // [8:21] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 1, // 8: user.SendMagicLinkResponse.session:type_name -> user.Session + 2, // 9: user.User.Create:input_type -> user.CreateRequest + 6, // 10: user.User.Read:input_type -> user.ReadRequest + 8, // 11: user.User.Update:input_type -> user.UpdateRequest + 4, // 12: user.User.Delete:input_type -> user.DeleteRequest + 10, // 13: user.User.UpdatePassword:input_type -> user.UpdatePasswordRequest + 14, // 14: user.User.Login:input_type -> user.LoginRequest + 16, // 15: user.User.Logout:input_type -> user.LogoutRequest + 12, // 16: user.User.ReadSession:input_type -> user.ReadSessionRequest + 18, // 17: user.User.VerifyEmail:input_type -> user.VerifyEmailRequest + 20, // 18: user.User.SendVerificationEmail:input_type -> user.SendVerificationEmailRequest + 22, // 19: user.User.SendPasswordResetEmail:input_type -> user.SendPasswordResetEmailRequest + 24, // 20: user.User.ResetPassword:input_type -> user.ResetPasswordRequest + 26, // 21: user.User.List:input_type -> user.ListRequest + 28, // 22: user.User.SendMagicLink:input_type -> user.SendMagicLinkRequest + 30, // 23: user.User.VerifyToken:input_type -> user.VerifyTokenRequest + 3, // 24: user.User.Create:output_type -> user.CreateResponse + 7, // 25: user.User.Read:output_type -> user.ReadResponse + 9, // 26: user.User.Update:output_type -> user.UpdateResponse + 5, // 27: user.User.Delete:output_type -> user.DeleteResponse + 11, // 28: user.User.UpdatePassword:output_type -> user.UpdatePasswordResponse + 15, // 29: user.User.Login:output_type -> user.LoginResponse + 17, // 30: user.User.Logout:output_type -> user.LogoutResponse + 13, // 31: user.User.ReadSession:output_type -> user.ReadSessionResponse + 19, // 32: user.User.VerifyEmail:output_type -> user.VerifyEmailResponse + 21, // 33: user.User.SendVerificationEmail:output_type -> user.SendVerificationEmailResponse + 23, // 34: user.User.SendPasswordResetEmail:output_type -> user.SendPasswordResetEmailResponse + 25, // 35: user.User.ResetPassword:output_type -> user.ResetPasswordResponse + 27, // 36: user.User.List:output_type -> user.ListResponse + 29, // 37: user.User.SendMagicLink:output_type -> user.SendMagicLinkResponse + 31, // 38: user.User.VerifyToken:output_type -> user.VerifyTokenResponse + 24, // [24:39] is the sub-list for method output_type + 9, // [9:24] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_proto_user_proto_init() } @@ -2274,6 +2565,54 @@ func file_proto_user_proto_init() { return nil } } + file_proto_user_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendMagicLinkRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_user_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendMagicLinkResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_user_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VerifyTokenRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_user_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VerifyTokenResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -2281,7 +2620,7 @@ func file_proto_user_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_user_proto_rawDesc, NumEnums: 0, - NumMessages: 31, + NumMessages: 35, NumExtensions: 0, NumServices: 1, }, diff --git a/user/proto/user.pb.micro.go b/user/proto/user.pb.micro.go index ea59448..8b9b49c 100644 --- a/user/proto/user.pb.micro.go +++ b/user/proto/user.pb.micro.go @@ -55,6 +55,8 @@ type UserService interface { SendPasswordResetEmail(ctx context.Context, in *SendPasswordResetEmailRequest, opts ...client.CallOption) (*SendPasswordResetEmailResponse, error) ResetPassword(ctx context.Context, in *ResetPasswordRequest, opts ...client.CallOption) (*ResetPasswordResponse, error) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) + SendMagicLink(ctx context.Context, in *SendMagicLinkRequest, opts ...client.CallOption) (User_SendMagicLinkService, error) + VerifyToken(ctx context.Context, in *VerifyTokenRequest, opts ...client.CallOption) (*VerifyTokenResponse, error) } type userService struct { @@ -199,6 +201,65 @@ func (c *userService) List(ctx context.Context, in *ListRequest, opts ...client. return out, nil } +func (c *userService) SendMagicLink(ctx context.Context, in *SendMagicLinkRequest, opts ...client.CallOption) (User_SendMagicLinkService, error) { + req := c.c.NewRequest(c.name, "User.SendMagicLink", &SendMagicLinkRequest{}) + stream, err := c.c.Stream(ctx, req, opts...) + if err != nil { + return nil, err + } + if err := stream.Send(in); err != nil { + return nil, err + } + return &userServiceSendMagicLink{stream}, nil +} + +type User_SendMagicLinkService interface { + Context() context.Context + SendMsg(interface{}) error + RecvMsg(interface{}) error + Close() error + Recv() (*SendMagicLinkResponse, error) +} + +type userServiceSendMagicLink struct { + stream client.Stream +} + +func (x *userServiceSendMagicLink) Close() error { + return x.stream.Close() +} + +func (x *userServiceSendMagicLink) Context() context.Context { + return x.stream.Context() +} + +func (x *userServiceSendMagicLink) SendMsg(m interface{}) error { + return x.stream.Send(m) +} + +func (x *userServiceSendMagicLink) RecvMsg(m interface{}) error { + return x.stream.Recv(m) +} + +func (x *userServiceSendMagicLink) Recv() (*SendMagicLinkResponse, error) { + m := new(SendMagicLinkResponse) + err := x.stream.Recv(m) + if err != nil { + return nil, err + } + return m, nil +} + +func (c *userService) VerifyToken(ctx context.Context, in *VerifyTokenRequest, opts ...client.CallOption) (*VerifyTokenResponse, error) { + req := c.c.NewRequest(c.name, "User.VerifyToken", in) + out := new(VerifyTokenResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for User service type UserHandler interface { @@ -215,6 +276,8 @@ type UserHandler interface { SendPasswordResetEmail(context.Context, *SendPasswordResetEmailRequest, *SendPasswordResetEmailResponse) error ResetPassword(context.Context, *ResetPasswordRequest, *ResetPasswordResponse) error List(context.Context, *ListRequest, *ListResponse) error + SendMagicLink(context.Context, *SendMagicLinkRequest, User_SendMagicLinkStream) error + VerifyToken(context.Context, *VerifyTokenRequest, *VerifyTokenResponse) error } func RegisterUserHandler(s server.Server, hdlr UserHandler, opts ...server.HandlerOption) error { @@ -232,6 +295,8 @@ func RegisterUserHandler(s server.Server, hdlr UserHandler, opts ...server.Handl SendPasswordResetEmail(ctx context.Context, in *SendPasswordResetEmailRequest, out *SendPasswordResetEmailResponse) error ResetPassword(ctx context.Context, in *ResetPasswordRequest, out *ResetPasswordResponse) error List(ctx context.Context, in *ListRequest, out *ListResponse) error + SendMagicLink(ctx context.Context, stream server.Stream) error + VerifyToken(ctx context.Context, in *VerifyTokenRequest, out *VerifyTokenResponse) error } type User struct { user @@ -295,3 +360,47 @@ func (h *userHandler) ResetPassword(ctx context.Context, in *ResetPasswordReques func (h *userHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error { return h.UserHandler.List(ctx, in, out) } + +func (h *userHandler) SendMagicLink(ctx context.Context, stream server.Stream) error { + m := new(SendMagicLinkRequest) + if err := stream.Recv(m); err != nil { + return err + } + return h.UserHandler.SendMagicLink(ctx, m, &userSendMagicLinkStream{stream}) +} + +type User_SendMagicLinkStream interface { + Context() context.Context + SendMsg(interface{}) error + RecvMsg(interface{}) error + Close() error + Send(*SendMagicLinkResponse) error +} + +type userSendMagicLinkStream struct { + stream server.Stream +} + +func (x *userSendMagicLinkStream) Close() error { + return x.stream.Close() +} + +func (x *userSendMagicLinkStream) Context() context.Context { + return x.stream.Context() +} + +func (x *userSendMagicLinkStream) SendMsg(m interface{}) error { + return x.stream.Send(m) +} + +func (x *userSendMagicLinkStream) RecvMsg(m interface{}) error { + return x.stream.Recv(m) +} + +func (x *userSendMagicLinkStream) Send(m *SendMagicLinkResponse) error { + return x.stream.Send(m) +} + +func (h *userHandler) VerifyToken(ctx context.Context, in *VerifyTokenRequest, out *VerifyTokenResponse) error { + return h.UserHandler.VerifyToken(ctx, in, out) +} diff --git a/user/proto/user.proto b/user/proto/user.proto index ef139c2..0180167 100644 --- a/user/proto/user.proto +++ b/user/proto/user.proto @@ -18,6 +18,8 @@ service User { rpc SendPasswordResetEmail(SendPasswordResetEmailRequest) returns (SendPasswordResetEmailResponse) {} rpc ResetPassword(ResetPasswordRequest) returns (ResetPasswordResponse) {} rpc List(ListRequest) returns(ListResponse) {} + rpc SendMagicLink(SendMagicLinkRequest) returns (stream SendMagicLinkResponse) {} + rpc VerifyToken(VerifyTokenRequest) returns (VerifyTokenResponse) {} } message Account { @@ -237,3 +239,38 @@ message ListRequest { message ListResponse { repeated Account users = 1; } + +// Login using email only - Passwordless +message SendMagicLinkRequest { + // the email address of the user + string email = 1; + string subject = 2; + // Text content of the email. Don't forget to include the string '$micro_verification_link' which will be replaced by the real verification link + // HTML emails are not available currently. + string textContent = 3; + // Display name of the sender for the email. Note: the email address will still be 'support@m3o.com' + string fromName = 4; + // Your web site address, example www.example.com or user.example.com + string address = 5; + // Endpoint name where your http request handler handles MagicLink by + // calling M3O VerifyToken endpoint. You can return as a result a success, + // failed or redirect to another page. + string endpoint = 6; +} + +message SendMagicLinkResponse { + Session session = 1; +} + +// Check whether the token attached to MagicLink is valid or not. +// Ideally, you need to call this endpoint from your http request +// handler that handles the endpoint which is specified in the +// SendMagicLink request. +message VerifyTokenRequest { + string token = 1; +} + +message VerifyTokenResponse { + bool is_valid = 1; + string message = 2; +}