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,29 +5,129 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
auditv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/audit/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"
|
||||
)
|
||||
|
||||
// AuditClient implements AuditServiceClient using gRPC.
|
||||
// This is a stub implementation - will be fully implemented when proto files are generated in Phase 4.
|
||||
type AuditClient struct {
|
||||
registry registry.ServiceRegistry
|
||||
conn *grpc.ClientConn
|
||||
client auditv1.AuditServiceClient
|
||||
}
|
||||
|
||||
// NewAuditClient creates a new gRPC client for the Audit Service.
|
||||
func NewAuditClient(reg registry.ServiceRegistry) (services.AuditServiceClient, error) {
|
||||
return &AuditClient{
|
||||
client := &AuditClient{
|
||||
registry: reg,
|
||||
}, nil
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// connect connects to the Audit Service.
|
||||
func (c *AuditClient) connect(ctx context.Context) error {
|
||||
if c.conn != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
instances, err := c.registry.Discover(ctx, "audit-service")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to discover audit service: %w", err)
|
||||
}
|
||||
|
||||
if len(instances) == 0 {
|
||||
return fmt.Errorf("no instances found for audit-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 audit-service at %s: %w", address, err)
|
||||
}
|
||||
|
||||
c.conn = conn
|
||||
c.client = auditv1.NewAuditServiceClient(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Record records an audit log entry.
|
||||
func (c *AuditClient) Record(ctx context.Context, entry *services.AuditLogEntry) error {
|
||||
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||
if err := c.connect(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := c.client.Record(ctx, &auditv1.RecordRequest{
|
||||
Entry: &auditv1.AuditLogEntry{
|
||||
UserId: entry.UserID,
|
||||
Action: entry.Action,
|
||||
Resource: entry.Resource,
|
||||
ResourceId: entry.ResourceID,
|
||||
IpAddress: entry.IPAddress,
|
||||
UserAgent: entry.UserAgent,
|
||||
Metadata: entry.Metadata,
|
||||
Timestamp: entry.Timestamp,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("record audit log failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Query queries audit logs based on filters.
|
||||
func (c *AuditClient) Query(ctx context.Context, filters *services.AuditLogFilters) ([]services.AuditLogEntry, error) {
|
||||
return nil, fmt.Errorf("not implemented: proto files not yet generated")
|
||||
if err := c.connect(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &auditv1.QueryRequest{
|
||||
Limit: int32(filters.Limit),
|
||||
Offset: int32(filters.Offset),
|
||||
}
|
||||
|
||||
if filters.UserID != nil {
|
||||
req.UserId = filters.UserID
|
||||
}
|
||||
if filters.Action != nil {
|
||||
req.Action = filters.Action
|
||||
}
|
||||
if filters.Resource != nil {
|
||||
req.Resource = filters.Resource
|
||||
}
|
||||
if filters.ResourceID != nil {
|
||||
req.ResourceId = filters.ResourceID
|
||||
}
|
||||
if filters.StartTime != nil {
|
||||
req.StartTime = filters.StartTime
|
||||
}
|
||||
if filters.EndTime != nil {
|
||||
req.EndTime = filters.EndTime
|
||||
}
|
||||
|
||||
resp, err := c.client.Query(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query audit logs failed: %w", err)
|
||||
}
|
||||
|
||||
entries := make([]services.AuditLogEntry, 0, len(resp.Entries))
|
||||
for _, e := range resp.Entries {
|
||||
entries = append(entries, services.AuditLogEntry{
|
||||
UserID: e.UserId,
|
||||
Action: e.Action,
|
||||
Resource: e.Resource,
|
||||
ResourceID: e.ResourceId,
|
||||
IPAddress: e.IpAddress,
|
||||
UserAgent: e.UserAgent,
|
||||
Metadata: e.Metadata,
|
||||
Timestamp: e.Timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
@@ -5,70 +5,132 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
authv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/auth/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"
|
||||
)
|
||||
|
||||
// AuthClient implements AuthServiceClient using gRPC.
|
||||
// This is a stub implementation - will be fully implemented when proto files are generated in Phase 4.
|
||||
type AuthClient struct {
|
||||
registry registry.ServiceRegistry
|
||||
// conn will be set when proto files are available
|
||||
// conn *grpc.ClientConn
|
||||
conn *grpc.ClientConn
|
||||
client authv1.AuthServiceClient
|
||||
}
|
||||
|
||||
// NewAuthClient creates a new gRPC client for the Auth Service.
|
||||
func NewAuthClient(reg registry.ServiceRegistry) (services.AuthServiceClient, error) {
|
||||
return &AuthClient{
|
||||
client := &AuthClient{
|
||||
registry: reg,
|
||||
}, nil
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// connect connects to the Auth Service.
|
||||
func (c *AuthClient) connect(ctx context.Context) error {
|
||||
if c.conn != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
instances, err := c.registry.Discover(ctx, "auth-service")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to discover auth service: %w", err)
|
||||
}
|
||||
|
||||
if len(instances) == 0 {
|
||||
return fmt.Errorf("no instances found for auth-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 auth-service at %s: %w", address, err)
|
||||
}
|
||||
|
||||
c.conn = conn
|
||||
c.client = authv1.NewAuthServiceClient(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Login authenticates a user and returns access and refresh tokens.
|
||||
func (c *AuthClient) Login(ctx context.Context, email, password string) (*services.TokenResponse, error) {
|
||||
// TODO: Implement when proto files are generated
|
||||
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.Login(ctx, &authv1.LoginRequest{
|
||||
Email: email,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("login failed: %w", err)
|
||||
}
|
||||
|
||||
return &services.TokenResponse{
|
||||
AccessToken: resp.AccessToken,
|
||||
RefreshToken: resp.RefreshToken,
|
||||
ExpiresIn: resp.ExpiresIn,
|
||||
TokenType: resp.TokenType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RefreshToken refreshes an access token using a refresh token.
|
||||
func (c *AuthClient) RefreshToken(ctx context.Context, refreshToken string) (*services.TokenResponse, error) {
|
||||
// TODO: Implement when proto files are generated
|
||||
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.RefreshToken(ctx, &authv1.RefreshTokenRequest{
|
||||
RefreshToken: refreshToken,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("refresh token failed: %w", err)
|
||||
}
|
||||
|
||||
return &services.TokenResponse{
|
||||
AccessToken: resp.AccessToken,
|
||||
RefreshToken: resp.RefreshToken,
|
||||
ExpiresIn: resp.ExpiresIn,
|
||||
TokenType: resp.TokenType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ValidateToken validates a JWT token and returns the token claims.
|
||||
func (c *AuthClient) ValidateToken(ctx context.Context, token string) (*services.TokenClaims, error) {
|
||||
// TODO: Implement when proto files are generated
|
||||
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.ValidateToken(ctx, &authv1.ValidateTokenRequest{
|
||||
Token: token,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validate token failed: %w", err)
|
||||
}
|
||||
|
||||
return &services.TokenClaims{
|
||||
UserID: resp.UserId,
|
||||
Email: resp.Email,
|
||||
Roles: resp.Roles,
|
||||
ExpiresAt: resp.ExpiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Logout invalidates a refresh token.
|
||||
func (c *AuthClient) Logout(ctx context.Context, refreshToken string) error {
|
||||
// TODO: Implement when proto files are generated
|
||||
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||
}
|
||||
if err := c.connect(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: connectToService will be implemented when proto files are generated
|
||||
// This function will discover and connect to a service instance via gRPC.
|
||||
// func connectToService(ctx context.Context, reg registry.ServiceRegistry, serviceName string) (*grpc.ClientConn, error) {
|
||||
// instances, err := reg.Discover(ctx, serviceName)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to discover service %s: %w", serviceName, err)
|
||||
// }
|
||||
//
|
||||
// if len(instances) == 0 {
|
||||
// return nil, fmt.Errorf("no instances found for service %s", serviceName)
|
||||
// }
|
||||
//
|
||||
// // Use the first healthy instance (load balancing can be added later)
|
||||
// instance := instances[0]
|
||||
// address := fmt.Sprintf("%s:%d", instance.Address, instance.Port)
|
||||
//
|
||||
// // Create gRPC connection
|
||||
// conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to connect to %s at %s: %w", serviceName, address, err)
|
||||
// }
|
||||
//
|
||||
// return conn, nil
|
||||
// }
|
||||
_, err := c.client.Logout(ctx, &authv1.LogoutRequest{
|
||||
RefreshToken: refreshToken,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("logout failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,39 +5,142 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
authzv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/authz/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"
|
||||
)
|
||||
|
||||
// AuthzClient implements AuthzServiceClient using gRPC.
|
||||
// This is a stub implementation - will be fully implemented when proto files are generated in Phase 4.
|
||||
type AuthzClient struct {
|
||||
registry registry.ServiceRegistry
|
||||
conn *grpc.ClientConn
|
||||
client authzv1.AuthzServiceClient
|
||||
}
|
||||
|
||||
// NewAuthzClient creates a new gRPC client for the Authz Service.
|
||||
func NewAuthzClient(reg registry.ServiceRegistry) (services.AuthzServiceClient, error) {
|
||||
return &AuthzClient{
|
||||
client := &AuthzClient{
|
||||
registry: reg,
|
||||
}, nil
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// connect connects to the Authz Service.
|
||||
func (c *AuthzClient) connect(ctx context.Context) error {
|
||||
if c.conn != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
instances, err := c.registry.Discover(ctx, "authz-service")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to discover authz service: %w", err)
|
||||
}
|
||||
|
||||
if len(instances) == 0 {
|
||||
return fmt.Errorf("no instances found for authz-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 authz-service at %s: %w", address, err)
|
||||
}
|
||||
|
||||
c.conn = conn
|
||||
c.client = authzv1.NewAuthzServiceClient(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authorize checks if a user has a specific permission and returns an error if not.
|
||||
func (c *AuthzClient) Authorize(ctx context.Context, userID, permission string) error {
|
||||
return fmt.Errorf("not implemented: proto files not yet generated")
|
||||
if err := c.connect(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := c.client.Authorize(ctx, &authzv1.AuthorizeRequest{
|
||||
UserId: userID,
|
||||
Permission: permission,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("authorize failed: %w", err)
|
||||
}
|
||||
|
||||
if !resp.Authorized {
|
||||
return fmt.Errorf("unauthorized: %s", resp.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasPermission checks if a user has a specific permission.
|
||||
func (c *AuthzClient) HasPermission(ctx context.Context, userID, permission string) (bool, error) {
|
||||
return false, fmt.Errorf("not implemented: proto files not yet generated")
|
||||
if err := c.connect(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resp, err := c.client.HasPermission(ctx, &authzv1.HasPermissionRequest{
|
||||
UserId: userID,
|
||||
Permission: permission,
|
||||
})
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("has permission check failed: %w", err)
|
||||
}
|
||||
|
||||
return resp.HasPermission, nil
|
||||
}
|
||||
|
||||
// GetUserPermissions returns all permissions for a user.
|
||||
func (c *AuthzClient) GetUserPermissions(ctx context.Context, userID string) ([]services.Permission, 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.GetUserPermissions(ctx, &authzv1.GetUserPermissionsRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get user permissions failed: %w", err)
|
||||
}
|
||||
|
||||
permissions := make([]services.Permission, 0, len(resp.Permissions))
|
||||
for _, p := range resp.Permissions {
|
||||
permissions = append(permissions, services.Permission{
|
||||
ID: p.Id,
|
||||
Code: p.Code,
|
||||
Name: p.Name,
|
||||
Description: p.Description,
|
||||
})
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
// GetUserRoles returns all roles for a user.
|
||||
func (c *AuthzClient) GetUserRoles(ctx context.Context, userID string) ([]services.Role, 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.GetUserRoles(ctx, &authzv1.GetUserRolesRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get user roles failed: %w", err)
|
||||
}
|
||||
|
||||
roles := make([]services.Role, 0, len(resp.Roles))
|
||||
for _, r := range resp.Roles {
|
||||
roles = append(roles, services.Role{
|
||||
ID: r.Id,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Permissions: r.Permissions,
|
||||
})
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
@@ -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