- Add VerifyPassword RPC to Identity Service - Added to proto file and generated code - Implemented in Identity Service gRPC server - Added to Identity Service client interface and gRPC client - Complete RefreshToken implementation - Store refresh tokens in database using RefreshToken entity - Validate refresh tokens with expiration checking - Revoke refresh tokens on logout and token rotation - Integrate Authz Service for role retrieval - Added AuthzServiceClient to Auth Service - Get user roles during login and token refresh - Gracefully handle Authz Service failures - Require JWT secret in configuration - Removed default secret fallback - Service fails to start if JWT secret is not configured - Fix Consul health checks for Docker - Services now register with Docker service names (e.g., audit-service) - Allows Consul (in Docker) to reach services via Docker DNS - Health checks use gRPC service names instead of localhost This completes all TODOs in auth_service_fx.go and fixes the Consul health check failures in Docker environments.
232 lines
6.2 KiB
Go
232 lines
6.2 KiB
Go
// Package grpc provides gRPC client implementations for service clients.
|
|
package grpc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
identityv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/identity/v1"
|
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
|
"git.dcentral.systems/toolz/goplt/pkg/services"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
)
|
|
|
|
// IdentityClient implements IdentityServiceClient using gRPC.
|
|
type IdentityClient struct {
|
|
registry registry.ServiceRegistry
|
|
conn *grpc.ClientConn
|
|
client identityv1.IdentityServiceClient
|
|
}
|
|
|
|
// NewIdentityClient creates a new gRPC client for the Identity Service.
|
|
func NewIdentityClient(reg registry.ServiceRegistry) (services.IdentityServiceClient, error) {
|
|
client := &IdentityClient{
|
|
registry: reg,
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
// connect connects to the Identity Service.
|
|
func (c *IdentityClient) connect(ctx context.Context) error {
|
|
if c.conn != nil {
|
|
return nil
|
|
}
|
|
|
|
instances, err := c.registry.Discover(ctx, "identity-service")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to discover identity service: %w", err)
|
|
}
|
|
|
|
if len(instances) == 0 {
|
|
return fmt.Errorf("no instances found for identity-service")
|
|
}
|
|
|
|
instance := instances[0]
|
|
address := fmt.Sprintf("%s:%d", instance.Address, instance.Port)
|
|
|
|
conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to identity-service at %s: %w", address, err)
|
|
}
|
|
|
|
c.conn = conn
|
|
c.client = identityv1.NewIdentityServiceClient(conn)
|
|
return nil
|
|
}
|
|
|
|
// GetUser retrieves a user by ID.
|
|
func (c *IdentityClient) GetUser(ctx context.Context, id string) (*services.User, error) {
|
|
if err := c.connect(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := c.client.GetUser(ctx, &identityv1.GetUserRequest{Id: id})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get user failed: %w", err)
|
|
}
|
|
|
|
return protoUserToServiceUser(resp.User), nil
|
|
}
|
|
|
|
// GetUserByEmail retrieves a user by email address.
|
|
func (c *IdentityClient) GetUserByEmail(ctx context.Context, email string) (*services.User, error) {
|
|
if err := c.connect(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := c.client.GetUserByEmail(ctx, &identityv1.GetUserByEmailRequest{Email: email})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get user by email failed: %w", err)
|
|
}
|
|
|
|
return protoUserToServiceUser(resp.User), nil
|
|
}
|
|
|
|
// CreateUser creates a new user.
|
|
func (c *IdentityClient) CreateUser(ctx context.Context, user *services.CreateUserRequest) (*services.User, error) {
|
|
if err := c.connect(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := c.client.CreateUser(ctx, &identityv1.CreateUserRequest{
|
|
Email: user.Email,
|
|
Username: user.Username,
|
|
Password: user.Password,
|
|
FirstName: user.FirstName,
|
|
LastName: user.LastName,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create user failed: %w", err)
|
|
}
|
|
|
|
return protoUserToServiceUser(resp.User), nil
|
|
}
|
|
|
|
// UpdateUser updates an existing user.
|
|
func (c *IdentityClient) UpdateUser(ctx context.Context, id string, user *services.UpdateUserRequest) (*services.User, error) {
|
|
if err := c.connect(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var email, username, firstName, lastName *string
|
|
if user.Email != nil && *user.Email != "" {
|
|
email = user.Email
|
|
}
|
|
if user.Username != nil && *user.Username != "" {
|
|
username = user.Username
|
|
}
|
|
if user.FirstName != nil && *user.FirstName != "" {
|
|
firstName = user.FirstName
|
|
}
|
|
if user.LastName != nil && *user.LastName != "" {
|
|
lastName = user.LastName
|
|
}
|
|
|
|
resp, err := c.client.UpdateUser(ctx, &identityv1.UpdateUserRequest{
|
|
Id: id,
|
|
Email: email,
|
|
Username: username,
|
|
FirstName: firstName,
|
|
LastName: lastName,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("update user failed: %w", err)
|
|
}
|
|
|
|
return protoUserToServiceUser(resp.User), nil
|
|
}
|
|
|
|
// DeleteUser deletes a user.
|
|
func (c *IdentityClient) DeleteUser(ctx context.Context, id string) error {
|
|
if err := c.connect(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err := c.client.DeleteUser(ctx, &identityv1.DeleteUserRequest{Id: id})
|
|
if err != nil {
|
|
return fmt.Errorf("delete user failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyEmail verifies a user's email address using a verification token.
|
|
func (c *IdentityClient) VerifyEmail(ctx context.Context, token string) error {
|
|
if err := c.connect(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err := c.client.VerifyEmail(ctx, &identityv1.VerifyEmailRequest{Token: token})
|
|
if err != nil {
|
|
return fmt.Errorf("verify email failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RequestPasswordReset requests a password reset token.
|
|
func (c *IdentityClient) RequestPasswordReset(ctx context.Context, email string) error {
|
|
if err := c.connect(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err := c.client.RequestPasswordReset(ctx, &identityv1.RequestPasswordResetRequest{Email: email})
|
|
if err != nil {
|
|
return fmt.Errorf("request password reset failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ResetPassword resets a user's password using a reset token.
|
|
func (c *IdentityClient) ResetPassword(ctx context.Context, token, newPassword string) error {
|
|
if err := c.connect(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err := c.client.ResetPassword(ctx, &identityv1.ResetPasswordRequest{
|
|
Token: token,
|
|
NewPassword: newPassword,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("reset password failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyPassword verifies a user's password and returns the user if valid.
|
|
func (c *IdentityClient) VerifyPassword(ctx context.Context, email, password string) (*services.User, error) {
|
|
if err := c.connect(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := c.client.VerifyPassword(ctx, &identityv1.VerifyPasswordRequest{
|
|
Email: email,
|
|
Password: password,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("verify password failed: %w", err)
|
|
}
|
|
|
|
return protoUserToServiceUser(resp.User), nil
|
|
}
|
|
|
|
// protoUserToServiceUser converts a proto User to a service User.
|
|
func protoUserToServiceUser(u *identityv1.User) *services.User {
|
|
if u == nil {
|
|
return nil
|
|
}
|
|
return &services.User{
|
|
ID: u.Id,
|
|
Email: u.Email,
|
|
Username: u.Username,
|
|
FirstName: u.FirstName,
|
|
LastName: u.LastName,
|
|
EmailVerified: u.EmailVerified,
|
|
CreatedAt: u.CreatedAt,
|
|
UpdatedAt: u.UpdatedAt,
|
|
}
|
|
}
|