You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
5.0 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. package server
  2. import (
  3. "context"
  4. "errors"
  5. "log"
  6. "time"
  7. "github.com/google/uuid"
  8. "golang.org/x/crypto/bcrypt"
  9. "gorm.io/gorm"
  10. "git.chrishayward.xyz/x/users/proto"
  11. "git.chrishayward.xyz/x/users/server/models"
  12. )
  13. type usersServer struct {
  14. proto.UsersServer
  15. secret *string
  16. db *gorm.DB
  17. }
  18. func NewUsersServer(secret *string, db *gorm.DB) proto.UsersServer {
  19. db.AutoMigrate(&models.User{}, &models.Session{}, &models.PasswordToken{})
  20. return &usersServer{
  21. secret: secret,
  22. db: db,
  23. }
  24. }
  25. func (m *usersServer) Register(ctx context.Context, in *proto.RegisterRequest) (*proto.RegisterResponse, error) {
  26. // Make sure both passwords are included and match.
  27. if in.Form.Password == nil || in.Form.PasswordAgain == nil {
  28. return nil, errors.New("Must include password(s).")
  29. }
  30. if *in.Form.Password != *in.Form.PasswordAgain {
  31. return nil, errors.New("Passwords do not match.")
  32. }
  33. // Check for an existing user.
  34. var user models.User
  35. tx := m.db.First(&user, "email = ?", in.Form.Email)
  36. if tx.RowsAffected != 0 {
  37. return nil, errors.New("User already exists.")
  38. }
  39. // Encode the password.
  40. bytes, err := bcrypt.GenerateFromPassword([]byte(*in.Form.Password), bcrypt.MaxCost)
  41. if err != nil {
  42. log.Fatalf("Failed to encode password: %v", err)
  43. return nil, errors.New("Failed to encode password.")
  44. }
  45. // Create the new user.
  46. user.Email = in.Form.Email
  47. user.Password = string(bytes)
  48. tx = m.db.Create(&user)
  49. if tx.RowsAffected == 0 {
  50. log.Fatalf("Failed to save user: %v", err)
  51. return nil, errors.New("Failed to save user.")
  52. }
  53. // Return the response.
  54. return &proto.RegisterResponse{}, nil
  55. }
  56. func (m *usersServer) Login(ctx context.Context, in *proto.LoginRequest) (*proto.LoginResponse, error) {
  57. // Make sure the password is included.
  58. if in.Form.Password == nil {
  59. return nil, errors.New("Password must be included.")
  60. }
  61. // Find the user.
  62. var user models.User
  63. tx := m.db.First(&user, "email = ?", in.Form.Email)
  64. if tx.RowsAffected == 0 {
  65. return nil, errors.New("User not found.")
  66. }
  67. // Compare the passwords.
  68. if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(*in.Form.Password)); err != nil {
  69. return nil, errors.New("Passwords do not match.")
  70. }
  71. // Create a session.
  72. session := &models.Session{
  73. Token: uuid.NewString(),
  74. Expires: time.Now().AddDate(0, 0, 1).UnixNano(),
  75. UserID: user.ID,
  76. }
  77. // Save the token.
  78. tx = m.db.Create(&session)
  79. if tx.RowsAffected == 0 {
  80. return nil, errors.New("Failed to create session.")
  81. }
  82. // Return the response.
  83. return &proto.LoginResponse{
  84. Token: &proto.UserToken{
  85. Token: session.Token,
  86. Expires: &session.Expires,
  87. },
  88. }, nil
  89. }
  90. func (m *usersServer) Authorize(ctx context.Context, in *proto.AuthorizeRequest) (*proto.AuthorizeResponse, error) {
  91. // Make sure the secrets match.
  92. if in.Secret != *m.secret {
  93. return nil, errors.New("Secrets do not match.")
  94. }
  95. // Find the session.
  96. var session models.Session
  97. tx := m.db.First(&session, "token = ?", in.Token.Token)
  98. if tx.RowsAffected == 0 {
  99. return nil, errors.New("Session not found.")
  100. }
  101. // Make sure the session hasn't expired.
  102. if time.Now().UnixNano() > session.Expires {
  103. return nil, errors.New("Token is expired.")
  104. }
  105. // Return the user ID.
  106. return &proto.AuthorizeResponse{
  107. User: &proto.UserInfo{
  108. Id: int64(session.UserID),
  109. },
  110. }, nil
  111. }
  112. func (m *usersServer) ResetPassword(ctx context.Context, in *proto.ResetPasswordRequest) (*proto.ResetPasswordResponse, error) {
  113. // Find the user.
  114. var user models.User
  115. tx := m.db.First(&user, "email = ?", in.Form.Email)
  116. if tx.RowsAffected == 0 {
  117. return nil, errors.New("User not found.")
  118. }
  119. // Generate a reset token.
  120. resetToken := &models.PasswordToken{
  121. UserID: user.ID,
  122. Token: uuid.NewString(),
  123. Expires: time.Now().UnixNano(),
  124. }
  125. // Save the token.
  126. tx = m.db.Create(resetToken)
  127. if tx.RowsAffected == 0 {
  128. return nil, errors.New("Failed to create token.")
  129. }
  130. // Return the response.
  131. return &proto.ResetPasswordResponse{
  132. Token: &proto.UserToken{
  133. Token: resetToken.Token,
  134. },
  135. }, nil
  136. }
  137. func (m *usersServer) ChangePassword(ctx context.Context, in *proto.ChangePasswordRequest) (*proto.ChangePasswordResponse, error) {
  138. // Find the reset token.
  139. var resetToken models.PasswordToken
  140. tx := m.db.First(&resetToken, "token = ?", in.Token.Token)
  141. if tx.RowsAffected == 0 {
  142. return nil, errors.New("Token not found.")
  143. }
  144. // Find the user.
  145. var user models.User
  146. tx = m.db.First(&user, "id = ?", resetToken.UserID)
  147. if tx.RowsAffected == 0 {
  148. return nil, errors.New("User not found.")
  149. }
  150. // Update the password.
  151. bytes, err := bcrypt.GenerateFromPassword([]byte(*in.Form.Password), bcrypt.MaxCost)
  152. if err != nil {
  153. log.Fatalf("Failed to encode password: %v", err)
  154. return nil, errors.New("Failed to encode password.")
  155. }
  156. user.Password = string(bytes)
  157. if tx = m.db.Save(user); tx.RowsAffected == 0 {
  158. return nil, errors.New("Failed to update password.")
  159. }
  160. // Expire current token.
  161. resetToken.Expires = time.Now().UnixNano()
  162. if tx = m.db.Save(&resetToken); tx.RowsAffected == 0 {
  163. return nil, errors.New("Failed to update password.")
  164. }
  165. // Return the response.
  166. return nil, nil
  167. }