feat(epic2): Implement core authentication and authorization services
- Implement Audit Service (2.5) - gRPC server with Record and Query operations - Database persistence with audit schema - Service registry integration - Entry point: cmd/audit-service - Implement Identity Service (2.2) - User CRUD operations - Password hashing with argon2id - Email verification and password reset flows - Entry point: cmd/identity-service - Fix package naming conflicts in user_service.go - Implement Auth Service (2.1) - JWT token generation and validation - Login, RefreshToken, ValidateToken, Logout RPCs - Integration with Identity Service - Entry point: cmd/auth-service - Note: RefreshToken entity needs Ent generation - Implement Authz Service (2.3, 2.4) - Permission checking and authorization - User roles and permissions retrieval - RBAC-based authorization - Entry point: cmd/authz-service - Implement gRPC clients for all services - Auth, Identity, Authz, and Audit clients - Service discovery integration - Full gRPC communication - Add service configurations to config/default.yaml - Create SUMMARY.md with implementation details and testing instructions - Fix compilation errors in Identity Service (password package conflicts) - All services build successfully and tests pass
This commit is contained in:
@@ -5,59 +5,210 @@ 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.
|
||||
// This is a stub implementation - will be fully implemented when proto files are generated in Phase 4.
|
||||
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) {
|
||||
return &IdentityClient{
|
||||
client := &IdentityClient{
|
||||
registry: reg,
|
||||
}, nil
|
||||
}
|
||||
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) {
|
||||
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||
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) {
|
||||
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||
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) {
|
||||
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||
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) {
|
||||
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||
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 {
|
||||
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||
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 {
|
||||
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||
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 {
|
||||
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||
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 {
|
||||
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||
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
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user