feat(auth): Complete Auth Service implementation and fix Consul health checks

- 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.
This commit is contained in:
2025-11-06 21:26:34 +01:00
parent b02c1d44c8
commit 04022b835e
34 changed files with 6775 additions and 90 deletions

View File

@@ -17,6 +17,7 @@ import (
"entgo.io/ent/dialect/sql/sqlgraph"
"git.dcentral.systems/toolz/goplt/internal/ent/auditlog"
"git.dcentral.systems/toolz/goplt/internal/ent/permission"
"git.dcentral.systems/toolz/goplt/internal/ent/refreshtoken"
"git.dcentral.systems/toolz/goplt/internal/ent/role"
"git.dcentral.systems/toolz/goplt/internal/ent/rolepermission"
"git.dcentral.systems/toolz/goplt/internal/ent/user"
@@ -32,6 +33,8 @@ type Client struct {
AuditLog *AuditLogClient
// Permission is the client for interacting with the Permission builders.
Permission *PermissionClient
// RefreshToken is the client for interacting with the RefreshToken builders.
RefreshToken *RefreshTokenClient
// Role is the client for interacting with the Role builders.
Role *RoleClient
// RolePermission is the client for interacting with the RolePermission builders.
@@ -53,6 +56,7 @@ func (c *Client) init() {
c.Schema = migrate.NewSchema(c.driver)
c.AuditLog = NewAuditLogClient(c.config)
c.Permission = NewPermissionClient(c.config)
c.RefreshToken = NewRefreshTokenClient(c.config)
c.Role = NewRoleClient(c.config)
c.RolePermission = NewRolePermissionClient(c.config)
c.User = NewUserClient(c.config)
@@ -151,6 +155,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
config: cfg,
AuditLog: NewAuditLogClient(cfg),
Permission: NewPermissionClient(cfg),
RefreshToken: NewRefreshTokenClient(cfg),
Role: NewRoleClient(cfg),
RolePermission: NewRolePermissionClient(cfg),
User: NewUserClient(cfg),
@@ -176,6 +181,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
config: cfg,
AuditLog: NewAuditLogClient(cfg),
Permission: NewPermissionClient(cfg),
RefreshToken: NewRefreshTokenClient(cfg),
Role: NewRoleClient(cfg),
RolePermission: NewRolePermissionClient(cfg),
User: NewUserClient(cfg),
@@ -209,7 +215,8 @@ func (c *Client) Close() error {
// In order to add hooks to a specific client, call: `client.Node.Use(...)`.
func (c *Client) Use(hooks ...Hook) {
for _, n := range []interface{ Use(...Hook) }{
c.AuditLog, c.Permission, c.Role, c.RolePermission, c.User, c.UserRole,
c.AuditLog, c.Permission, c.RefreshToken, c.Role, c.RolePermission, c.User,
c.UserRole,
} {
n.Use(hooks...)
}
@@ -219,7 +226,8 @@ func (c *Client) Use(hooks ...Hook) {
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
func (c *Client) Intercept(interceptors ...Interceptor) {
for _, n := range []interface{ Intercept(...Interceptor) }{
c.AuditLog, c.Permission, c.Role, c.RolePermission, c.User, c.UserRole,
c.AuditLog, c.Permission, c.RefreshToken, c.Role, c.RolePermission, c.User,
c.UserRole,
} {
n.Intercept(interceptors...)
}
@@ -232,6 +240,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.AuditLog.mutate(ctx, m)
case *PermissionMutation:
return c.Permission.mutate(ctx, m)
case *RefreshTokenMutation:
return c.RefreshToken.mutate(ctx, m)
case *RoleMutation:
return c.Role.mutate(ctx, m)
case *RolePermissionMutation:
@@ -527,6 +537,139 @@ func (c *PermissionClient) mutate(ctx context.Context, m *PermissionMutation) (V
}
}
// RefreshTokenClient is a client for the RefreshToken schema.
type RefreshTokenClient struct {
config
}
// NewRefreshTokenClient returns a client for the RefreshToken from the given config.
func NewRefreshTokenClient(c config) *RefreshTokenClient {
return &RefreshTokenClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `refreshtoken.Hooks(f(g(h())))`.
func (c *RefreshTokenClient) Use(hooks ...Hook) {
c.hooks.RefreshToken = append(c.hooks.RefreshToken, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `refreshtoken.Intercept(f(g(h())))`.
func (c *RefreshTokenClient) Intercept(interceptors ...Interceptor) {
c.inters.RefreshToken = append(c.inters.RefreshToken, interceptors...)
}
// Create returns a builder for creating a RefreshToken entity.
func (c *RefreshTokenClient) Create() *RefreshTokenCreate {
mutation := newRefreshTokenMutation(c.config, OpCreate)
return &RefreshTokenCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of RefreshToken entities.
func (c *RefreshTokenClient) CreateBulk(builders ...*RefreshTokenCreate) *RefreshTokenCreateBulk {
return &RefreshTokenCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *RefreshTokenClient) MapCreateBulk(slice any, setFunc func(*RefreshTokenCreate, int)) *RefreshTokenCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &RefreshTokenCreateBulk{err: fmt.Errorf("calling to RefreshTokenClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*RefreshTokenCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &RefreshTokenCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for RefreshToken.
func (c *RefreshTokenClient) Update() *RefreshTokenUpdate {
mutation := newRefreshTokenMutation(c.config, OpUpdate)
return &RefreshTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *RefreshTokenClient) UpdateOne(_m *RefreshToken) *RefreshTokenUpdateOne {
mutation := newRefreshTokenMutation(c.config, OpUpdateOne, withRefreshToken(_m))
return &RefreshTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *RefreshTokenClient) UpdateOneID(id string) *RefreshTokenUpdateOne {
mutation := newRefreshTokenMutation(c.config, OpUpdateOne, withRefreshTokenID(id))
return &RefreshTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for RefreshToken.
func (c *RefreshTokenClient) Delete() *RefreshTokenDelete {
mutation := newRefreshTokenMutation(c.config, OpDelete)
return &RefreshTokenDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *RefreshTokenClient) DeleteOne(_m *RefreshToken) *RefreshTokenDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *RefreshTokenClient) DeleteOneID(id string) *RefreshTokenDeleteOne {
builder := c.Delete().Where(refreshtoken.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &RefreshTokenDeleteOne{builder}
}
// Query returns a query builder for RefreshToken.
func (c *RefreshTokenClient) Query() *RefreshTokenQuery {
return &RefreshTokenQuery{
config: c.config,
ctx: &QueryContext{Type: TypeRefreshToken},
inters: c.Interceptors(),
}
}
// Get returns a RefreshToken entity by its id.
func (c *RefreshTokenClient) Get(ctx context.Context, id string) (*RefreshToken, error) {
return c.Query().Where(refreshtoken.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *RefreshTokenClient) GetX(ctx context.Context, id string) *RefreshToken {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// Hooks returns the client hooks.
func (c *RefreshTokenClient) Hooks() []Hook {
return c.hooks.RefreshToken
}
// Interceptors returns the client interceptors.
func (c *RefreshTokenClient) Interceptors() []Interceptor {
return c.inters.RefreshToken
}
func (c *RefreshTokenClient) mutate(ctx context.Context, m *RefreshTokenMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&RefreshTokenCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&RefreshTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&RefreshTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&RefreshTokenDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown RefreshToken mutation op: %q", m.Op())
}
}
// RoleClient is a client for the Role schema.
type RoleClient struct {
config
@@ -1174,9 +1317,11 @@ func (c *UserRoleClient) mutate(ctx context.Context, m *UserRoleMutation) (Value
// hooks and interceptors per client, for fast access.
type (
hooks struct {
AuditLog, Permission, Role, RolePermission, User, UserRole []ent.Hook
AuditLog, Permission, RefreshToken, Role, RolePermission, User,
UserRole []ent.Hook
}
inters struct {
AuditLog, Permission, Role, RolePermission, User, UserRole []ent.Interceptor
AuditLog, Permission, RefreshToken, Role, RolePermission, User,
UserRole []ent.Interceptor
}
)