|
|
package server
import ( "context" "errors" "log" "time"
"github.com/google/uuid" "golang.org/x/crypto/bcrypt"
"git.chrishayward.xyz/x/users/proto" )
type usersServer struct { proto.UsersServer secret *string users UserDB tokens TokenDB resetTokens TokenDB }
func NewUsersServer(secret *string) proto.UsersServer { return &usersServer{ secret: secret, users: newInMemoryUserDB(), tokens: newInMemoryTokenDB(), resetTokens: newInMemoryTokenDB(), } }
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.
if u, _ := m.users.FindByEmail(in.Form.Email); u != nil { 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.
if err := m.users.Save(&User{ Email: in.Form.Email, Password: string(bytes), }); err != nil { 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.
user, err := m.users.FindByEmail(in.Form.Email) if err != nil { return nil, err }
// 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 token.
expires := time.Now().AddDate(0, 0, 1) token := &Token{ UserID: user.ID, Token: uuid.NewString(), Expires: &expires, }
// Save the token.
m.tokens.Save(token)
// Return the response.
expiresNano := expires.UnixNano() return &proto.LoginResponse{ Token: &proto.UserToken{ Token: token.Token, Expires: &expiresNano, }, }, 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 token.
token, err := m.tokens.FindByToken(in.Token.Token) if err != nil { return nil, err }
// Make sure the token hasn't expired.
if token.Expires.After(time.Now()) { return nil, errors.New("Token is expired.") }
// Return the user ID.
return &proto.AuthorizeResponse{ User: &proto.UserInfo{ Id: int64(token.UserID), }, }, nil }
func (m *usersServer) ResetPassword(ctx context.Context, in *proto.ResetPasswordRequest) (*proto.ResetPasswordResponse, error) { // Find the user.
user, err := m.users.FindByEmail(in.Form.Email) if err != nil { return nil, err }
// Generate a reset token.
expires := time.Now().AddDate(0, 0, 1) token := &Token{ UserID: user.ID, Token: uuid.NewString(), Expires: &expires, }
// Save the token.
if err := m.resetTokens.Save(token); err != nil { return nil, err }
// Return the response.
return &proto.ResetPasswordResponse{ Token: &proto.UserToken{ Token: token.Token, }, }, nil }
func (m *usersServer) ChangePassword(ctx context.Context, in *proto.ChangePasswordRequest) (*proto.ChangePasswordResponse, error) { // Find the reset token.
resetToken, err := m.resetTokens.FindByToken(in.Token.Token) if err != nil { return nil, err }
// Find the user.
user, err := m.users.FindByID(resetToken.UserID) if err != nil { return nil, err }
// 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 err := m.users.Save(user); err != nil { return nil, err }
// Expire current token.
if token, err := m.tokens.FindByUserID(user.ID); token != nil && err == nil { expires := time.Now() token.Expires = &expires _ = m.tokens.Save(token) }
// Return the response.
return nil, nil }
|