|
|
package server
import ( "context" "errors" "log" "time"
"github.com/google/uuid" "golang.org/x/crypto/bcrypt" "gorm.io/gorm"
"git.chrishayward.xyz/x/users/proto" "git.chrishayward.xyz/x/users/server/models" )
type usersServer struct { proto.UsersServer secret *string db *gorm.DB }
func NewUsersServer(secret *string, db *gorm.DB) proto.UsersServer { db.AutoMigrate(&models.User{}, &models.Session{}, &models.PasswordToken{}) return &usersServer{ secret: secret, db: db, } }
func (m *usersServer) Register(ctx context.Context, in *proto.RegisterRequest) (*proto.RegisterResponse, error) { // Make sure both passwords are included and match.
if in.Form.Password == nil || in.Form.PasswordAgain == nil { return nil, errors.New("Must include password(s).") }
if *in.Form.Password != *in.Form.PasswordAgain { return nil, errors.New("Passwords do not match.") }
// Check for an existing user.
var user models.User tx := m.db.First(&user, "email = ?", in.Form.Email) if tx.RowsAffected != 0 { return nil, errors.New("User already exists.") }
// Encode the password.
bytes, err := bcrypt.GenerateFromPassword([]byte(*in.Form.Password), bcrypt.MaxCost) if err != nil { log.Fatalf("Failed to encode password: %v", err) return nil, errors.New("Failed to encode password.") }
// Create the new user.
user.Email = in.Form.Email user.Password = string(bytes) tx = m.db.Create(&user) if tx.RowsAffected == 0 { log.Fatalf("Failed to save user: %v", err) return nil, errors.New("Failed to save user.") }
// Return the response.
return &proto.RegisterResponse{}, nil }
func (m *usersServer) Login(ctx context.Context, in *proto.LoginRequest) (*proto.LoginResponse, error) { // Make sure the password is included.
if in.Form.Password == nil { return nil, errors.New("Password must be included.") }
// Find the user.
var user models.User tx := m.db.First(&user, "email = ?", in.Form.Email) if tx.RowsAffected == 0 { return nil, errors.New("User not found.") }
// Compare the passwords.
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(*in.Form.Password)); err != nil { return nil, errors.New("Passwords do not match.") }
// Create a session.
session := &models.Session{ Token: uuid.NewString(), Expires: time.Now().AddDate(0, 0, 1).UnixNano(), UserID: user.ID, }
// Save the token.
tx = m.db.Create(&session) if tx.RowsAffected == 0 { return nil, errors.New("Failed to create session.") }
// Return the response.
return &proto.LoginResponse{ Token: &proto.UserToken{ Token: session.Token, Expires: &session.Expires, }, }, nil }
func (m *usersServer) Authorize(ctx context.Context, in *proto.AuthorizeRequest) (*proto.AuthorizeResponse, error) { // Make sure the secrets match.
if in.Secret != *m.secret { return nil, errors.New("Secrets do not match.") }
// Find the session.
var session models.Session tx := m.db.First(&session, "token = ?", in.Token.Token) if tx.RowsAffected == 0 { return nil, errors.New("Session not found.") }
// Make sure the session hasn't expired.
if time.Now().UnixNano() > session.Expires { return nil, errors.New("Token is expired.") }
// Return the user ID.
return &proto.AuthorizeResponse{ User: &proto.UserInfo{ Id: int64(session.UserID), }, }, nil }
func (m *usersServer) ResetPassword(ctx context.Context, in *proto.ResetPasswordRequest) (*proto.ResetPasswordResponse, error) { // Find the user.
var user models.User tx := m.db.First(&user, "email = ?", in.Form.Email) if tx.RowsAffected == 0 { return nil, errors.New("User not found.") }
// Generate a reset token.
resetToken := &models.PasswordToken{ UserID: user.ID, Token: uuid.NewString(), Expires: time.Now().UnixNano(), }
// Save the token.
tx = m.db.Create(resetToken) if tx.RowsAffected == 0 { return nil, errors.New("Failed to create token.") }
// Return the response.
return &proto.ResetPasswordResponse{ Token: &proto.UserToken{ Token: resetToken.Token, }, }, nil }
func (m *usersServer) ChangePassword(ctx context.Context, in *proto.ChangePasswordRequest) (*proto.ChangePasswordResponse, error) { // Find the reset token.
var resetToken models.PasswordToken tx := m.db.First(&resetToken, "token = ?", in.Token.Token) if tx.RowsAffected == 0 { return nil, errors.New("Token not found.") }
// Find the user.
var user models.User tx = m.db.First(&user, "id = ?", resetToken.UserID) if tx.RowsAffected == 0 { return nil, errors.New("User not found.") }
// Update the password.
bytes, err := bcrypt.GenerateFromPassword([]byte(*in.Form.Password), bcrypt.MaxCost) if err != nil { log.Fatalf("Failed to encode password: %v", err) return nil, errors.New("Failed to encode password.") }
user.Password = string(bytes) if tx = m.db.Save(user); tx.RowsAffected == 0 { return nil, errors.New("Failed to update password.") }
// Expire current token.
resetToken.Expires = time.Now().UnixNano() if tx = m.db.Save(&resetToken); tx.RowsAffected == 0 { return nil, errors.New("Failed to update password.") }
// Return the response.
return nil, nil }
|