Uecko_ERP/docs/architecture/identity-backend-architecture.md

3.5 KiB

Identity Backend Architecture

Objetivo del módulo identity

identity sustituye progresivamente al módulo legacy auth.

Responsabilidades previstas:

  • cuentas autenticables
  • login/password
  • access token
  • refresh token rotado
  • logout
  • sesión actual
  • empresas accesibles
  • memberships
  • roles
  • permisos
  • autorización futura

Dominio mínimo creado

Agregados principales:

Account
Company
CompanyMembership
Role
RefreshToken

Value Objects reutilizados

Desde @repo/rdx-ddd:

UniqueID
LanguageCode
Name
TextValue
UtcDate
EmailAddress
URLAddress

VOs específicos de identity:

PasswordHash
RefreshTokenHash
RoleCode
AccountStatus
CompanyStatus
CompanyMembershipStatus

Seguridad

Servicios definidos:

IPasswordHasher
IAccessTokenIssuer
IAccessTokenVerifier
IRefreshTokenGenerator
IRefreshTokenHasher

Implementaciones actuales:

BcryptPasswordHasher
JsonWebTokenAccessTokenIssuer
JsonWebTokenAccessTokenVerifier
CryptoRefreshTokenGenerator
Sha256RefreshTokenHasher

Decisiones:

  • password hashing con bcrypt en V1.
  • access token JWT con accountId y email.
  • refresh token aleatorio con crypto.randomBytes.
  • refresh token persistido solo como hash SHA-256.
  • no incluir companyId, roles ni permisos en access token.

Persistencia mínima

Tablas/modelos creados:

identity_accounts
identity_refresh_tokens

Repositorios:

IAccountRepository
IRefreshTokenRepository
SequelizeAccountRepository
SequelizeRefreshTokenRepository

Servicios públicos del módulo

identity registra un servicio público:

identity:general

Incluye:

auth.authenticatedOnly(): RequestHandler[];
auth.tenantRequired(): RequestHandler[];

Pero los consumidores no deberían usar directamente identity:general en routers.

Deben usar los builders públicos:

requireIdentityAuthenticated(params: StartParams): RequestHandler[];
requireIdentityTenant(params: StartParams): RequestHandler[];

Uso correcto en routers

import { requireIdentityTenant } from "@erp/identity/api";

export function buildSomeRoutes(params: StartParams) {
  const router = Router();

  router.use(...requireIdentityTenant(params));

  return router;
}

Shape actual de req.user

{
  userId: UniqueID;
  email?: EmailAddress;
  companyId?: UniqueID;
  roles?: string[];
}

Compatibilidad provisional:

  • roles puede ser [].
  • companyId viene de X-Company-Id.
  • companySlug no debe depender de auth.

Prohibiciones de diseño

No usar en módulos consumidores:

IdentityInternalDeps;
authenticateUserDependencies;
getInternal("identity");

No crear:

<module>-auth-middlewares.ts

como patrón estable.

No añadir a access token:

companyId
roles
permissions
companySlug

Permisos

Cada módulo define sus propios permisos.

core contiene contratos transversales mínimos:

export type PermissionCode = string;

export type PermissionDefinition = {
  code: PermissionCode;
  module: string;
  description: string;
};

identity define solo sus permisos propios:

identity:companies:update
identity:roles:read
identity:roles:create
identity:roles:update
identity:roles:delete
identity:company-users:read
identity:company-users:update-roles
identity:company-users:disable
identity:company-users:enable
identity:permissions:read

Role.permissionCodes usa PermissionCode[], no IdentityPermissionCode[].

La existencia real de permisos se valida en Application con IPermissionCatalog.