@adatechnology/user-contracts 0.1.0-rc.1 → 0.1.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -22,6 +22,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
24
  AUTH_PROVIDER_TYPE: () => AUTH_PROVIDER_TYPE,
25
+ AVATAR_CONTENT_TYPES: () => AVATAR_CONTENT_TYPES,
26
+ AVATAR_MAX_BYTES: () => AVATAR_MAX_BYTES,
27
+ AVATAR_REJECTION: () => AVATAR_REJECTION,
28
+ AvatarRejectedError: () => AvatarRejectedError,
25
29
  ConfigMissingError: () => ConfigMissingError,
26
30
  EmailAlreadyExistsError: () => EmailAlreadyExistsError,
27
31
  InvalidCredentialsError: () => InvalidCredentialsError,
@@ -36,6 +40,7 @@ __export(index_exports, {
36
40
  UserError: () => UserError,
37
41
  UserNotFoundError: () => UserNotFoundError,
38
42
  WeakPasswordError: () => WeakPasswordError,
43
+ checkAvatar: () => checkAvatar,
39
44
  confirmPasswordResetSchema: () => confirmPasswordResetSchema,
40
45
  createUserSchema: () => createUserSchema,
41
46
  keycloakCallbackSchema: () => keycloakCallbackSchema,
@@ -45,6 +50,28 @@ __export(index_exports, {
45
50
  });
46
51
  module.exports = __toCommonJS(index_exports);
47
52
 
53
+ // src/avatar.ts
54
+ var AVATAR_MAX_BYTES = 2 * 1024 * 1024;
55
+ var AVATAR_CONTENT_TYPES = [
56
+ "image/jpeg",
57
+ "image/png",
58
+ "image/webp"
59
+ ];
60
+ var AVATAR_REJECTION = {
61
+ TOO_LARGE: "avatar_too_large",
62
+ UNSUPPORTED_TYPE: "avatar_unsupported_type",
63
+ EMPTY: "avatar_empty"
64
+ };
65
+ function checkAvatar(params) {
66
+ if (params.byteLength <= 0) return AVATAR_REJECTION.EMPTY;
67
+ if (params.byteLength > AVATAR_MAX_BYTES) return AVATAR_REJECTION.TOO_LARGE;
68
+ if (!AVATAR_CONTENT_TYPES.includes(params.contentType)) {
69
+ return AVATAR_REJECTION.UNSUPPORTED_TYPE;
70
+ }
71
+ return void 0;
72
+ }
73
+ __name(checkAvatar, "checkAvatar");
74
+
48
75
  // src/errors.ts
49
76
  var USER_ERROR_CODE = {
50
77
  INVALID_CREDENTIALS: "USER_INVALID_CREDENTIALS",
@@ -57,7 +84,8 @@ var USER_ERROR_CODE = {
57
84
  WEAK_PASSWORD: "USER_WEAK_PASSWORD",
58
85
  PROVIDER_MISCONFIGURED: "USER_PROVIDER_MISCONFIGURED",
59
86
  PROVIDER_DISABLED: "USER_PROVIDER_DISABLED",
60
- CONFIG_MISSING: "USER_CONFIG_MISSING"
87
+ CONFIG_MISSING: "USER_CONFIG_MISSING",
88
+ AVATAR_REJECTED: "USER_AVATAR_REJECTED"
61
89
  };
62
90
  var UserError = class extends Error {
63
91
  static {
@@ -221,6 +249,22 @@ var ConfigMissingError = class extends UserError {
221
249
  this.name = "ConfigMissingError";
222
250
  }
223
251
  };
252
+ var AvatarRejectedError = class extends UserError {
253
+ static {
254
+ __name(this, "AvatarRejectedError");
255
+ }
256
+ constructor(reason) {
257
+ super({
258
+ message: "Avatar rejected",
259
+ statusCode: 400,
260
+ code: USER_ERROR_CODE.AVATAR_REJECTED,
261
+ details: {
262
+ reason
263
+ }
264
+ });
265
+ this.name = "AvatarRejectedError";
266
+ }
267
+ };
224
268
 
225
269
  // src/events.ts
226
270
  var USER_EVENT = {
@@ -271,6 +315,10 @@ var keycloakCallbackSchema = import_zod.z.object({
271
315
  // Annotate the CommonJS export names for ESM import in node:
272
316
  0 && (module.exports = {
273
317
  AUTH_PROVIDER_TYPE,
318
+ AVATAR_CONTENT_TYPES,
319
+ AVATAR_MAX_BYTES,
320
+ AVATAR_REJECTION,
321
+ AvatarRejectedError,
274
322
  ConfigMissingError,
275
323
  EmailAlreadyExistsError,
276
324
  InvalidCredentialsError,
@@ -285,6 +333,7 @@ var keycloakCallbackSchema = import_zod.z.object({
285
333
  UserError,
286
334
  UserNotFoundError,
287
335
  WeakPasswordError,
336
+ checkAvatar,
288
337
  confirmPasswordResetSchema,
289
338
  createUserSchema,
290
339
  keycloakCallbackSchema,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/events.ts","../src/providers.ts","../src/schemas.ts"],"sourcesContent":["/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nexport * from './user.types'\nexport * from './errors'\nexport * from './events'\nexport * from './providers'\nexport * from './schemas'\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nexport const USER_ERROR_CODE = {\n INVALID_CREDENTIALS: 'USER_INVALID_CREDENTIALS',\n USER_NOT_FOUND: 'USER_NOT_FOUND',\n EMAIL_ALREADY_EXISTS: 'USER_EMAIL_ALREADY_EXISTS',\n NOT_AUTHENTICATED: 'USER_NOT_AUTHENTICATED',\n RESET_TOKEN_INVALID: 'USER_RESET_TOKEN_INVALID',\n RESET_TOKEN_EXPIRED: 'USER_RESET_TOKEN_EXPIRED',\n RESET_TOKEN_ALREADY_USED: 'USER_RESET_TOKEN_ALREADY_USED',\n WEAK_PASSWORD: 'USER_WEAK_PASSWORD',\n PROVIDER_MISCONFIGURED: 'USER_PROVIDER_MISCONFIGURED',\n PROVIDER_DISABLED: 'USER_PROVIDER_DISABLED',\n CONFIG_MISSING: 'USER_CONFIG_MISSING',\n} as const\n\nexport type UserErrorCode = (typeof USER_ERROR_CODE)[keyof typeof USER_ERROR_CODE]\n\nexport class UserError extends Error {\n readonly statusCode: number\n readonly code: UserErrorCode\n readonly details?: unknown\n\n constructor(params: { message: string; statusCode: number; code: UserErrorCode; details?: unknown }) {\n super(params.message)\n this.name = 'UserError'\n this.statusCode = params.statusCode\n this.code = params.code\n this.details = params.details\n }\n}\n\nexport class InvalidCredentialsError extends UserError {\n constructor() {\n super({\n message: 'Invalid credentials',\n statusCode: 401,\n code: USER_ERROR_CODE.INVALID_CREDENTIALS,\n })\n this.name = 'InvalidCredentialsError'\n }\n}\n\nexport class UserNotFoundError extends UserError {\n constructor() {\n super({\n message: 'User not found',\n statusCode: 404,\n code: USER_ERROR_CODE.USER_NOT_FOUND,\n })\n this.name = 'UserNotFoundError'\n }\n}\n\nexport class EmailAlreadyExistsError extends UserError {\n constructor() {\n super({\n message: 'Email already exists',\n statusCode: 409,\n code: USER_ERROR_CODE.EMAIL_ALREADY_EXISTS,\n })\n this.name = 'EmailAlreadyExistsError'\n }\n}\n\nexport class NotAuthenticatedError extends UserError {\n constructor() {\n super({\n message: 'Not authenticated',\n statusCode: 401,\n code: USER_ERROR_CODE.NOT_AUTHENTICATED,\n })\n this.name = 'NotAuthenticatedError'\n }\n}\n\nexport class ResetTokenInvalidError extends UserError {\n constructor() {\n super({\n message: 'Invalid reset token',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_INVALID,\n })\n this.name = 'ResetTokenInvalidError'\n }\n}\n\nexport class ResetTokenExpiredError extends UserError {\n constructor() {\n super({\n message: 'Reset token expired',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_EXPIRED,\n })\n this.name = 'ResetTokenExpiredError'\n }\n}\n\nexport class ResetTokenAlreadyUsedError extends UserError {\n constructor() {\n super({\n message: 'Reset token already used',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_ALREADY_USED,\n })\n this.name = 'ResetTokenAlreadyUsedError'\n }\n}\n\nexport class WeakPasswordError extends UserError {\n constructor() {\n super({\n message: 'Password does not meet security requirements',\n statusCode: 400,\n code: USER_ERROR_CODE.WEAK_PASSWORD,\n })\n this.name = 'WeakPasswordError'\n }\n}\n\nexport class ProviderMisconfiguredError extends UserError {\n constructor(details?: unknown) {\n super({\n message: 'Authentication provider is misconfigured',\n statusCode: 500,\n code: USER_ERROR_CODE.PROVIDER_MISCONFIGURED,\n details,\n })\n this.name = 'ProviderMisconfiguredError'\n }\n}\n\nexport class ProviderDisabledError extends UserError {\n constructor() {\n super({\n message: 'Authentication provider is not available',\n statusCode: 503,\n code: USER_ERROR_CODE.PROVIDER_DISABLED,\n })\n this.name = 'ProviderDisabledError'\n }\n}\n\nexport class ConfigMissingError extends UserError {\n constructor(field: string) {\n super({\n message: `Missing required configuration: ${field}`,\n statusCode: 500,\n code: USER_ERROR_CODE.CONFIG_MISSING,\n details: { field },\n })\n this.name = 'ConfigMissingError'\n }\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport type { UserProfile } from './user.types'\n\nexport const USER_EVENT = {\n USER_CREATED: 'user.user.created',\n USER_UPDATED: 'user.user.updated',\n LOGIN_SUCCEEDED: 'user.login.succeeded',\n LOGIN_FAILED: 'user.login.failed',\n PASSWORD_CHANGED: 'user.password.changed',\n PASSWORD_RESET_REQUESTED: 'user.password_reset.requested',\n PASSWORD_RESET_COMPLETED: 'user.password_reset.completed',\n PROFILE_UPDATED: 'user.profile.updated',\n} as const\n\nexport type UserEventType = (typeof USER_EVENT)[keyof typeof USER_EVENT]\n\nexport type BaseEvent = {\n readonly companyId?: string\n readonly occurredAt: Date\n}\n\nexport type UserCreatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.USER_CREATED\n readonly userId: string\n readonly email: string\n}\n\nexport type UserUpdatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.USER_UPDATED\n readonly userId: string\n readonly user: UserProfile\n}\n\nexport type LoginSucceededEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.LOGIN_SUCCEEDED\n readonly userId: string\n readonly email: string\n readonly ipAddress: string\n}\n\nexport type LoginFailedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.LOGIN_FAILED\n readonly email: string\n readonly ipAddress: string\n readonly reason: string\n}\n\nexport type PasswordChangedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_CHANGED\n readonly userId: string\n}\n\nexport type PasswordResetRequestedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_RESET_REQUESTED\n readonly email: string\n readonly resetUrl: string\n}\n\nexport type PasswordResetCompletedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_RESET_COMPLETED\n readonly userId: string\n readonly email: string\n}\n\nexport type ProfileUpdatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PROFILE_UPDATED\n readonly userId: string\n readonly user: UserProfile\n}\n\nexport type UserDomainEvent =\n | UserCreatedEvent\n | UserUpdatedEvent\n | LoginSucceededEvent\n | LoginFailedEvent\n | PasswordChangedEvent\n | PasswordResetRequestedEvent\n | PasswordResetCompletedEvent\n | ProfileUpdatedEvent\n\nexport type UserHooks = {\n readonly onUserCreated?: (event: UserCreatedEvent) => Promise<void> | void\n readonly onUserUpdated?: (event: UserUpdatedEvent) => Promise<void> | void\n readonly onLoginSucceeded?: (event: LoginSucceededEvent) => Promise<void> | void\n readonly onLoginFailed?: (event: LoginFailedEvent) => Promise<void> | void\n readonly onPasswordChanged?: (event: PasswordChangedEvent) => Promise<void> | void\n readonly onPasswordResetRequested?: (event: PasswordResetRequestedEvent) => Promise<void> | void\n readonly onPasswordResetCompleted?: (event: PasswordResetCompletedEvent) => Promise<void> | void\n readonly onProfileUpdated?: (event: ProfileUpdatedEvent) => Promise<void> | void\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport type { UserSession } from './user.types'\n\nexport const AUTH_PROVIDER_TYPE = {\n LOCAL: 'local',\n OAUTH2: 'oauth2',\n OIDC: 'oidc',\n} as const\n\nexport type AuthProviderType = (typeof AUTH_PROVIDER_TYPE)[keyof typeof AUTH_PROVIDER_TYPE]\n\nexport type AuthProviderInterface<TCredentials = unknown> = {\n readonly id: string\n readonly type: AuthProviderType\n authenticate(params: { readonly credentials: TCredentials; readonly ipAddress?: string }): Promise<UserSession>\n}\n\nexport type AttributeMappingRule<TClaims extends Record<string, unknown>> =\n | { readonly from: keyof TClaims }\n | { readonly value: string }\n\nexport type AttributeMapping<TClaims extends Record<string, unknown>> = {\n readonly email: AttributeMappingRule<TClaims>\n readonly name?: AttributeMappingRule<TClaims>\n readonly role?: AttributeMappingRule<TClaims>\n}\n\n/**\n * `issue` devolve o token cru; `rotate`/`revoke` recebem o **sha256 hex** dele — o token cru nunca\n * chega ao armazenamento, então um dump do Redis ou da tabela não vale sessão.\n *\n * `revokeAllForUser` é o que faz uma troca de senha realmente encerrar as sessões abertas. Sem ela,\n * a sessão comprometida sobrevive à redefinição feita justamente para matá-la — por isso é\n * obrigatória no contrato, e não uma capacidade opcional.\n */\nexport type RefreshTokenStorePort = {\n issue(params: { readonly userId: string; readonly expiresInSeconds: number }): Promise<string>\n rotate(params: {\n readonly tokenHash: string\n readonly newExpiresInSeconds: number\n }): Promise<{ readonly token: string; readonly userId: string } | null>\n revoke(params: { readonly tokenHash: string }): Promise<void>\n revokeAllForUser(params: { readonly userId: string }): Promise<void>\n}\n\nexport type SendEmailParams = {\n readonly to: string\n readonly subject: string\n readonly body: string\n readonly html?: string\n}\n\nexport type DeliveryAttemptResult = {\n readonly success: boolean\n readonly messageId?: string\n readonly error?: string\n}\n\nexport type EmailDriverPort = {\n readonly driver: string\n send(params: SendEmailParams): Promise<DeliveryAttemptResult>\n}\n\nexport type ClockPort = {\n now(): Date\n}\n\nexport type LogMeta = Readonly<Record<string, unknown>>\n\n/** Mesma forma do `LoggerPort` dos outros contratos do ecossistema — o host escreve um adapter só. */\nexport type LoggerPort = {\n error(message: string, meta?: LogMeta): void\n warn(message: string, meta?: LogMeta): void\n info(message: string, meta?: LogMeta): void\n debug(message: string, meta?: LogMeta): void\n}\n\nexport type TenancyConfig = { readonly mode: 'single'; readonly defaultCompanyId: string } | { readonly mode: 'multi' }\n\n/**\n * `issuer`/`audience` são opcionais, mas quando declarados valem na assinatura **e** na\n * verificação: um token emitido para outra plateia é recusado. Um host que já emite JWT por conta\n * própria (migração gradual, dois emissores sobre o mesmo segredo) precisa declarar os mesmos\n * valores aqui, ou os dois lados não reconhecem o token um do outro.\n */\nexport type AccessTokenConfig = {\n readonly secret: string\n readonly expiresInSeconds?: number\n readonly issuer?: string\n readonly audience?: string\n}\n\nexport type RefreshTokenConfig = {\n readonly expiresInSeconds?: number\n}\n\nexport type PasswordResetConfig = {\n readonly resetUrlTemplate: string // must contain {token}\n readonly tokenExpiresInSeconds?: number\n}\n\nexport type KeycloakConfig = {\n readonly realm: string\n readonly authServerUrl: string\n readonly clientId: string\n readonly clientSecret?: string\n readonly attributeMapping?: AttributeMapping<Record<string, unknown>>\n}\n\nexport type UserModuleConfig = {\n readonly tenancy: TenancyConfig\n readonly accessToken: AccessTokenConfig\n readonly refreshToken?: RefreshTokenConfig\n readonly passwordReset?: PasswordResetConfig\n readonly keycloak?: KeycloakConfig\n}\n\n/**\n * `keycloak` não entra aqui: a verificação de token é plumbing específico do módulo (ver\n * `KeycloakVerifierPort` em `user-module`), não um contrato genérico reutilizável por outro host.\n */\nexport type UserModuleProviders = {\n readonly refreshTokenStore?: RefreshTokenStorePort\n readonly email?: EmailDriverPort\n readonly clock?: ClockPort\n readonly logger?: LoggerPort\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport { z } from 'zod'\n\nconst PASSWORD_MIN_LENGTH = 8\nconst PASSWORD_MAX_LENGTH = 128\n\nexport const localCredentialsSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n password: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n})\n\nexport type LocalCredentials = z.infer<typeof localCredentialsSchema>\n\nexport const requestPasswordResetSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n})\n\nexport type RequestPasswordResetInput = z.infer<typeof requestPasswordResetSchema>\n\nexport const confirmPasswordResetSchema = z.object({\n token: z.string().min(1),\n newPassword: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n})\n\nexport type ConfirmPasswordResetInput = z.infer<typeof confirmPasswordResetSchema>\n\nexport const updateProfileSchema = z.object({\n name: z.string().trim().min(1).max(255).optional(),\n})\n\nexport type UpdateProfileInput = z.infer<typeof updateProfileSchema>\n\nexport const createUserSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n name: z.string().trim().min(1).max(255),\n password: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n role: z.string().min(1).max(40),\n})\n\nexport type CreateUserInput = z.infer<typeof createUserSchema>\n\n/**\n * `accessToken`, não `code`/`state`: o módulo verifica localmente um token já emitido pelo\n * Keycloak (`@adatechnology/auth-keycloak`, verificação via JWKS) — a troca do `code` da\n * authorization code flow é responsabilidade do host/frontend, antes de chegar aqui.\n */\nexport const keycloakCallbackSchema = z.object({\n accessToken: z.string().min(1),\n})\n\nexport type KeycloakCallbackInput = z.infer<typeof keycloakCallbackSchema>\n"],"mappings":"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","names":["USER_ERROR_CODE","INVALID_CREDENTIALS","USER_NOT_FOUND","EMAIL_ALREADY_EXISTS","NOT_AUTHENTICATED","RESET_TOKEN_INVALID","RESET_TOKEN_EXPIRED","RESET_TOKEN_ALREADY_USED","WEAK_PASSWORD","PROVIDER_MISCONFIGURED","PROVIDER_DISABLED","CONFIG_MISSING","UserError","Error","statusCode","code","details","params","message","name","InvalidCredentialsError","UserNotFoundError","EmailAlreadyExistsError","NotAuthenticatedError","ResetTokenInvalidError","ResetTokenExpiredError","ResetTokenAlreadyUsedError","WeakPasswordError","ProviderMisconfiguredError","ProviderDisabledError","ConfigMissingError","field","USER_EVENT","USER_CREATED","USER_UPDATED","LOGIN_SUCCEEDED","LOGIN_FAILED","PASSWORD_CHANGED","PASSWORD_RESET_REQUESTED","PASSWORD_RESET_COMPLETED","PROFILE_UPDATED","AUTH_PROVIDER_TYPE","LOCAL","OAUTH2","OIDC","PASSWORD_MIN_LENGTH","PASSWORD_MAX_LENGTH","localCredentialsSchema","z","object","email","string","trim","toLowerCase","max","password","min","requestPasswordResetSchema","confirmPasswordResetSchema","token","newPassword","updateProfileSchema","name","optional","createUserSchema","role","keycloakCallbackSchema","accessToken"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/avatar.ts","../src/errors.ts","../src/events.ts","../src/providers.ts","../src/schemas.ts"],"sourcesContent":["/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nexport * from './user.types'\nexport * from './avatar'\nexport * from './errors'\nexport * from './events'\nexport * from './providers'\nexport * from './schemas'\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * A foto de perfil: porta de armazenamento e a validação que roda antes de qualquer rede.\n */\n\n/** 2 MB. Foto de perfil é exibida em 40px numa tabela; o que passa disso é desperdício de banda. */\nexport const AVATAR_MAX_BYTES = 2 * 1024 * 1024\n\n/**\n * Lista fechada, e não `image/*`: `image/svg+xml` é um documento com script dentro, e servido do\n * mesmo domínio viraria XSS. Nenhum destes três executa nada.\n */\nexport const AVATAR_CONTENT_TYPES = ['image/jpeg', 'image/png', 'image/webp'] as const\nexport type AvatarContentType = (typeof AVATAR_CONTENT_TYPES)[number]\n\nexport const AVATAR_REJECTION = {\n TOO_LARGE: 'avatar_too_large',\n UNSUPPORTED_TYPE: 'avatar_unsupported_type',\n EMPTY: 'avatar_empty',\n} as const\nexport type AvatarRejection = (typeof AVATAR_REJECTION)[keyof typeof AVATAR_REJECTION]\n\nexport type CheckAvatarParams = {\n readonly contentType: string\n readonly byteLength: number\n}\n\n/**\n * Devolve o motivo, e não um booleano: quem chama precisa dizer à pessoa se o arquivo é grande\n * demais ou se é do tipo errado — são duas correções diferentes.\n */\nexport function checkAvatar(params: CheckAvatarParams): AvatarRejection | undefined {\n if (params.byteLength <= 0) return AVATAR_REJECTION.EMPTY\n if (params.byteLength > AVATAR_MAX_BYTES) return AVATAR_REJECTION.TOO_LARGE\n if (!AVATAR_CONTENT_TYPES.includes(params.contentType as AvatarContentType)) {\n return AVATAR_REJECTION.UNSUPPORTED_TYPE\n }\n return undefined\n}\n\nexport type PutAvatarParams = {\n readonly userId: string\n readonly body: Uint8Array\n readonly contentType: AvatarContentType\n}\n\n/**\n * O host pluga o armazenamento; o módulo não sabe se é S3, disco ou memória.\n *\n * Sem esta porta o módulo não publica as rotas de foto — capacidade por ausência. Um produto sem\n * bucket não tem uma foto quebrada: não tem foto.\n */\nexport type AvatarStoragePort = {\n /** Grava e devolve a chave opaca a guardar na linha do usuário. */\n put(params: PutAvatarParams): Promise<string>\n /**\n * URL de leitura de vida curta.\n *\n * Assinada, e não pública: um rosto de funcionário não é o logo da empresa, e bucket aberto\n * indexa. Curta porque ela viaja na resposta da listagem, que passa por log e por cache.\n */\n sign(key: string): Promise<string>\n /** Remove a foto anterior; falhar aqui não pode derrubar a troca (o lixo é varrido depois). */\n remove?(key: string): Promise<void>\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nexport const USER_ERROR_CODE = {\n INVALID_CREDENTIALS: 'USER_INVALID_CREDENTIALS',\n USER_NOT_FOUND: 'USER_NOT_FOUND',\n EMAIL_ALREADY_EXISTS: 'USER_EMAIL_ALREADY_EXISTS',\n NOT_AUTHENTICATED: 'USER_NOT_AUTHENTICATED',\n RESET_TOKEN_INVALID: 'USER_RESET_TOKEN_INVALID',\n RESET_TOKEN_EXPIRED: 'USER_RESET_TOKEN_EXPIRED',\n RESET_TOKEN_ALREADY_USED: 'USER_RESET_TOKEN_ALREADY_USED',\n WEAK_PASSWORD: 'USER_WEAK_PASSWORD',\n PROVIDER_MISCONFIGURED: 'USER_PROVIDER_MISCONFIGURED',\n PROVIDER_DISABLED: 'USER_PROVIDER_DISABLED',\n CONFIG_MISSING: 'USER_CONFIG_MISSING',\n AVATAR_REJECTED: 'USER_AVATAR_REJECTED',\n} as const\n\nexport type UserErrorCode = (typeof USER_ERROR_CODE)[keyof typeof USER_ERROR_CODE]\n\nexport class UserError extends Error {\n readonly statusCode: number\n readonly code: UserErrorCode\n readonly details?: unknown\n\n constructor(params: { message: string; statusCode: number; code: UserErrorCode; details?: unknown }) {\n super(params.message)\n this.name = 'UserError'\n this.statusCode = params.statusCode\n this.code = params.code\n this.details = params.details\n }\n}\n\nexport class InvalidCredentialsError extends UserError {\n constructor() {\n super({\n message: 'Invalid credentials',\n statusCode: 401,\n code: USER_ERROR_CODE.INVALID_CREDENTIALS,\n })\n this.name = 'InvalidCredentialsError'\n }\n}\n\nexport class UserNotFoundError extends UserError {\n constructor() {\n super({\n message: 'User not found',\n statusCode: 404,\n code: USER_ERROR_CODE.USER_NOT_FOUND,\n })\n this.name = 'UserNotFoundError'\n }\n}\n\nexport class EmailAlreadyExistsError extends UserError {\n constructor() {\n super({\n message: 'Email already exists',\n statusCode: 409,\n code: USER_ERROR_CODE.EMAIL_ALREADY_EXISTS,\n })\n this.name = 'EmailAlreadyExistsError'\n }\n}\n\nexport class NotAuthenticatedError extends UserError {\n constructor() {\n super({\n message: 'Not authenticated',\n statusCode: 401,\n code: USER_ERROR_CODE.NOT_AUTHENTICATED,\n })\n this.name = 'NotAuthenticatedError'\n }\n}\n\nexport class ResetTokenInvalidError extends UserError {\n constructor() {\n super({\n message: 'Invalid reset token',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_INVALID,\n })\n this.name = 'ResetTokenInvalidError'\n }\n}\n\nexport class ResetTokenExpiredError extends UserError {\n constructor() {\n super({\n message: 'Reset token expired',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_EXPIRED,\n })\n this.name = 'ResetTokenExpiredError'\n }\n}\n\nexport class ResetTokenAlreadyUsedError extends UserError {\n constructor() {\n super({\n message: 'Reset token already used',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_ALREADY_USED,\n })\n this.name = 'ResetTokenAlreadyUsedError'\n }\n}\n\nexport class WeakPasswordError extends UserError {\n constructor() {\n super({\n message: 'Password does not meet security requirements',\n statusCode: 400,\n code: USER_ERROR_CODE.WEAK_PASSWORD,\n })\n this.name = 'WeakPasswordError'\n }\n}\n\nexport class ProviderMisconfiguredError extends UserError {\n constructor(details?: unknown) {\n super({\n message: 'Authentication provider is misconfigured',\n statusCode: 500,\n code: USER_ERROR_CODE.PROVIDER_MISCONFIGURED,\n details,\n })\n this.name = 'ProviderMisconfiguredError'\n }\n}\n\nexport class ProviderDisabledError extends UserError {\n constructor() {\n super({\n message: 'Authentication provider is not available',\n statusCode: 503,\n code: USER_ERROR_CODE.PROVIDER_DISABLED,\n })\n this.name = 'ProviderDisabledError'\n }\n}\n\nexport class ConfigMissingError extends UserError {\n constructor(field: string) {\n super({\n message: `Missing required configuration: ${field}`,\n statusCode: 500,\n code: USER_ERROR_CODE.CONFIG_MISSING,\n details: { field },\n })\n this.name = 'ConfigMissingError'\n }\n}\n\n/**\n * O `details.reason` e o motivo estavel (`AvatarRejection`), nao a frase.\n *\n * \"Grande demais\" e \"tipo nao suportado\" pedem correcoes diferentes, e quem monta a tela precisa\n * distinguir os dois sem casar string traduzida.\n */\nexport class AvatarRejectedError extends UserError {\n constructor(reason: string) {\n super({\n message: 'Avatar rejected',\n statusCode: 400,\n code: USER_ERROR_CODE.AVATAR_REJECTED,\n details: { reason },\n })\n this.name = 'AvatarRejectedError'\n }\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport type { UserProfile } from './user.types'\n\nexport const USER_EVENT = {\n USER_CREATED: 'user.user.created',\n USER_UPDATED: 'user.user.updated',\n LOGIN_SUCCEEDED: 'user.login.succeeded',\n LOGIN_FAILED: 'user.login.failed',\n PASSWORD_CHANGED: 'user.password.changed',\n PASSWORD_RESET_REQUESTED: 'user.password_reset.requested',\n PASSWORD_RESET_COMPLETED: 'user.password_reset.completed',\n PROFILE_UPDATED: 'user.profile.updated',\n} as const\n\nexport type UserEventType = (typeof USER_EVENT)[keyof typeof USER_EVENT]\n\nexport type BaseEvent = {\n readonly companyId?: string\n readonly occurredAt: Date\n}\n\nexport type UserCreatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.USER_CREATED\n readonly userId: string\n readonly email: string\n}\n\nexport type UserUpdatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.USER_UPDATED\n readonly userId: string\n readonly user: UserProfile\n}\n\nexport type LoginSucceededEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.LOGIN_SUCCEEDED\n readonly userId: string\n readonly email: string\n readonly ipAddress: string\n}\n\nexport type LoginFailedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.LOGIN_FAILED\n readonly email: string\n readonly ipAddress: string\n readonly reason: string\n}\n\nexport type PasswordChangedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_CHANGED\n readonly userId: string\n}\n\nexport type PasswordResetRequestedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_RESET_REQUESTED\n readonly email: string\n readonly resetUrl: string\n}\n\nexport type PasswordResetCompletedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_RESET_COMPLETED\n readonly userId: string\n readonly email: string\n}\n\nexport type ProfileUpdatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PROFILE_UPDATED\n readonly userId: string\n readonly user: UserProfile\n}\n\nexport type UserDomainEvent =\n | UserCreatedEvent\n | UserUpdatedEvent\n | LoginSucceededEvent\n | LoginFailedEvent\n | PasswordChangedEvent\n | PasswordResetRequestedEvent\n | PasswordResetCompletedEvent\n | ProfileUpdatedEvent\n\nexport type UserHooks = {\n readonly onUserCreated?: (event: UserCreatedEvent) => Promise<void> | void\n readonly onUserUpdated?: (event: UserUpdatedEvent) => Promise<void> | void\n readonly onLoginSucceeded?: (event: LoginSucceededEvent) => Promise<void> | void\n readonly onLoginFailed?: (event: LoginFailedEvent) => Promise<void> | void\n readonly onPasswordChanged?: (event: PasswordChangedEvent) => Promise<void> | void\n readonly onPasswordResetRequested?: (event: PasswordResetRequestedEvent) => Promise<void> | void\n readonly onPasswordResetCompleted?: (event: PasswordResetCompletedEvent) => Promise<void> | void\n readonly onProfileUpdated?: (event: ProfileUpdatedEvent) => Promise<void> | void\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport type { UserSession } from './user.types'\n\nexport const AUTH_PROVIDER_TYPE = {\n LOCAL: 'local',\n OAUTH2: 'oauth2',\n OIDC: 'oidc',\n} as const\n\nexport type AuthProviderType = (typeof AUTH_PROVIDER_TYPE)[keyof typeof AUTH_PROVIDER_TYPE]\n\nexport type AuthProviderInterface<TCredentials = unknown> = {\n readonly id: string\n readonly type: AuthProviderType\n authenticate(params: { readonly credentials: TCredentials; readonly ipAddress?: string }): Promise<UserSession>\n}\n\nexport type AttributeMappingRule<TClaims extends Record<string, unknown>> =\n | { readonly from: keyof TClaims }\n | { readonly value: string }\n\nexport type AttributeMapping<TClaims extends Record<string, unknown>> = {\n readonly email: AttributeMappingRule<TClaims>\n readonly name?: AttributeMappingRule<TClaims>\n readonly role?: AttributeMappingRule<TClaims>\n}\n\n/**\n * `issue` devolve o token cru; `rotate`/`revoke` recebem o **sha256 hex** dele — o token cru nunca\n * chega ao armazenamento, então um dump do Redis ou da tabela não vale sessão.\n *\n * `revokeAllForUser` é o que faz uma troca de senha realmente encerrar as sessões abertas. Sem ela,\n * a sessão comprometida sobrevive à redefinição feita justamente para matá-la — por isso é\n * obrigatória no contrato, e não uma capacidade opcional.\n */\nexport type RefreshTokenStorePort = {\n issue(params: { readonly userId: string; readonly expiresInSeconds: number }): Promise<string>\n rotate(params: {\n readonly tokenHash: string\n readonly newExpiresInSeconds: number\n }): Promise<{ readonly token: string; readonly userId: string } | null>\n revoke(params: { readonly tokenHash: string }): Promise<void>\n revokeAllForUser(params: { readonly userId: string }): Promise<void>\n}\n\n/**\n * Forma **idêntica** à de `@adatechnology/notification-contracts` — redeclarada, e não importada,\n * só para um pacote de contratos não arrastar outro domínio de runtime junto. Como o TypeScript é\n * estrutural, qualquer driver de `@adatechnology/email-provider` (`createSmtpEmailProvider`,\n * `createResendEmailProvider`, `createSesEmailProvider`) entra direto em `providers.email`, sem\n * adapter no host.\n *\n * Divergir daqui é o que quebra essa troca: a redeclaração só serve se as duas formas forem a\n * mesma, e nada no build de um pacote isolado avisa quando deixam de ser.\n */\nexport type SendEmailParams = {\n readonly to: string\n readonly subject: string\n readonly html: string\n readonly text: string\n readonly replyTo?: string\n readonly idempotencyKey?: string\n}\n\n/**\n * União discriminada, não `{ success: boolean }`: quem chama precisa separar endereço inválido\n * (suprimir, nunca reenviar) de falha temporária (reagendar com backoff) de falha definitiva.\n * Colapsar isso num booleano joga fora justamente a informação que decide a ação seguinte.\n */\nexport type DeliveryAttemptResult =\n | { readonly outcome: 'sent'; readonly providerMessageId?: string }\n | { readonly outcome: 'invalid_target'; readonly errorCode: string }\n | { readonly outcome: 'retriable'; readonly errorCode: string; readonly retryAfterSeconds?: number }\n | { readonly outcome: 'permanent'; readonly errorCode: string }\n\nexport type EmailDriverPort = {\n readonly driver: string\n send(params: SendEmailParams): Promise<DeliveryAttemptResult>\n}\n\nexport type ClockPort = {\n now(): Date\n}\n\nexport type LogMeta = Readonly<Record<string, unknown>>\n\n/** Mesma forma do `LoggerPort` dos outros contratos do ecossistema — o host escreve um adapter só. */\nexport type LoggerPort = {\n error(message: string, meta?: LogMeta): void\n warn(message: string, meta?: LogMeta): void\n info(message: string, meta?: LogMeta): void\n debug(message: string, meta?: LogMeta): void\n}\n\nexport type TenancyConfig = { readonly mode: 'single'; readonly defaultCompanyId: string } | { readonly mode: 'multi' }\n\n/**\n * `issuer`/`audience` são opcionais, mas quando declarados valem na assinatura **e** na\n * verificação: um token emitido para outra plateia é recusado. Um host que já emite JWT por conta\n * própria (migração gradual, dois emissores sobre o mesmo segredo) precisa declarar os mesmos\n * valores aqui, ou os dois lados não reconhecem o token um do outro.\n */\nexport type AccessTokenConfig = {\n readonly secret: string\n readonly expiresInSeconds?: number\n readonly issuer?: string\n readonly audience?: string\n}\n\nexport type RefreshTokenConfig = {\n readonly expiresInSeconds?: number\n}\n\nexport type PasswordResetEmailContent = {\n readonly subject: string\n readonly html: string\n readonly text: string\n}\n\nexport type PasswordResetEmailParams = {\n readonly resetUrl: string\n readonly name: string\n readonly expiresInSeconds: number\n}\n\nexport type PasswordResetConfig = {\n readonly resetUrlTemplate: string // must contain {token}\n readonly tokenExpiresInSeconds?: number\n /**\n * Texto do e-mail de redefinição. O módulo traz um padrão neutro, sem nome de produto e sem\n * marca — copy é vocabulário do host (`pluggable-module.md`), e cinco produtos consomem este\n * pacote. Quem quiser template versionado e pré-visualizável monta aqui em cima de\n * `renderTemplate` do `@adatechnology/notification-contracts`.\n */\n readonly buildEmail?: (params: PasswordResetEmailParams) => PasswordResetEmailContent\n}\n\nexport type KeycloakConfig = {\n readonly realm: string\n readonly authServerUrl: string\n readonly clientId: string\n readonly clientSecret?: string\n readonly attributeMapping?: AttributeMapping<Record<string, unknown>>\n}\n\nexport type UserModuleConfig = {\n readonly tenancy: TenancyConfig\n readonly accessToken: AccessTokenConfig\n readonly refreshToken?: RefreshTokenConfig\n readonly passwordReset?: PasswordResetConfig\n readonly keycloak?: KeycloakConfig\n}\n\n/**\n * `keycloak` não entra aqui: a verificação de token é plumbing específico do módulo (ver\n * `KeycloakVerifierPort` em `user-module`), não um contrato genérico reutilizável por outro host.\n */\nexport type UserModuleProviders = {\n readonly refreshTokenStore?: RefreshTokenStorePort\n readonly email?: EmailDriverPort\n readonly clock?: ClockPort\n readonly logger?: LoggerPort\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport { z } from 'zod'\n\nconst PASSWORD_MIN_LENGTH = 8\nconst PASSWORD_MAX_LENGTH = 128\n\nexport const localCredentialsSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n password: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n})\n\nexport type LocalCredentials = z.infer<typeof localCredentialsSchema>\n\nexport const requestPasswordResetSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n})\n\nexport type RequestPasswordResetInput = z.infer<typeof requestPasswordResetSchema>\n\nexport const confirmPasswordResetSchema = z.object({\n token: z.string().min(1),\n newPassword: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n})\n\nexport type ConfirmPasswordResetInput = z.infer<typeof confirmPasswordResetSchema>\n\nexport const updateProfileSchema = z.object({\n name: z.string().trim().min(1).max(255).optional(),\n})\n\nexport type UpdateProfileInput = z.infer<typeof updateProfileSchema>\n\nexport const createUserSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n name: z.string().trim().min(1).max(255),\n password: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n role: z.string().min(1).max(40),\n})\n\nexport type CreateUserInput = z.infer<typeof createUserSchema>\n\n/**\n * `accessToken`, não `code`/`state`: o módulo verifica localmente um token já emitido pelo\n * Keycloak (`@adatechnology/auth-keycloak`, verificação via JWKS) — a troca do `code` da\n * authorization code flow é responsabilidade do host/frontend, antes de chegar aqui.\n */\nexport const keycloakCallbackSchema = z.object({\n accessToken: z.string().min(1),\n})\n\nexport type KeycloakCallbackInput = z.infer<typeof keycloakCallbackSchema>\n"],"mappings":"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","names":["AVATAR_MAX_BYTES","AVATAR_CONTENT_TYPES","AVATAR_REJECTION","TOO_LARGE","UNSUPPORTED_TYPE","EMPTY","checkAvatar","params","byteLength","includes","contentType","undefined","USER_ERROR_CODE","INVALID_CREDENTIALS","USER_NOT_FOUND","EMAIL_ALREADY_EXISTS","NOT_AUTHENTICATED","RESET_TOKEN_INVALID","RESET_TOKEN_EXPIRED","RESET_TOKEN_ALREADY_USED","WEAK_PASSWORD","PROVIDER_MISCONFIGURED","PROVIDER_DISABLED","CONFIG_MISSING","AVATAR_REJECTED","UserError","Error","statusCode","code","details","params","message","name","InvalidCredentialsError","UserNotFoundError","EmailAlreadyExistsError","NotAuthenticatedError","ResetTokenInvalidError","ResetTokenExpiredError","ResetTokenAlreadyUsedError","WeakPasswordError","ProviderMisconfiguredError","ProviderDisabledError","ConfigMissingError","field","AvatarRejectedError","reason","USER_EVENT","USER_CREATED","USER_UPDATED","LOGIN_SUCCEEDED","LOGIN_FAILED","PASSWORD_CHANGED","PASSWORD_RESET_REQUESTED","PASSWORD_RESET_COMPLETED","PROFILE_UPDATED","AUTH_PROVIDER_TYPE","LOCAL","OAUTH2","OIDC","PASSWORD_MIN_LENGTH","PASSWORD_MAX_LENGTH","localCredentialsSchema","z","object","email","string","trim","toLowerCase","max","password","min","requestPasswordResetSchema","confirmPasswordResetSchema","token","newPassword","updateProfileSchema","name","optional","createUserSchema","role","keycloakCallbackSchema","accessToken"]}
package/dist/index.d.cts CHANGED
@@ -15,6 +15,13 @@ type UserProfile = {
15
15
  readonly companyId?: string;
16
16
  readonly isActive: boolean;
17
17
  readonly lastSeenAt?: Date;
18
+ /**
19
+ * URL de leitura assinada, presente so quando ha foto E o host plugou armazenamento.
20
+ *
21
+ * Ausente cobre os dois casos porque a tela faz a mesma coisa nos dois: desenha as iniciais. Um
22
+ * campo separado dizendo "tem foto, mas nao consigo servir" nao mudaria um pixel.
23
+ */
24
+ readonly avatarUrl?: string;
18
25
  };
19
26
  type UserSession = {
20
27
  readonly accessToken: string;
@@ -38,6 +45,59 @@ type ListUsersParams = {
38
45
  readonly companyId?: string;
39
46
  };
40
47
 
48
+ /**
49
+ * Copyright (c) 2026 Ada Technology. MIT License.
50
+ *
51
+ * A foto de perfil: porta de armazenamento e a validação que roda antes de qualquer rede.
52
+ */
53
+ /** 2 MB. Foto de perfil é exibida em 40px numa tabela; o que passa disso é desperdício de banda. */
54
+ declare const AVATAR_MAX_BYTES: number;
55
+ /**
56
+ * Lista fechada, e não `image/*`: `image/svg+xml` é um documento com script dentro, e servido do
57
+ * mesmo domínio viraria XSS. Nenhum destes três executa nada.
58
+ */
59
+ declare const AVATAR_CONTENT_TYPES: readonly ["image/jpeg", "image/png", "image/webp"];
60
+ type AvatarContentType = (typeof AVATAR_CONTENT_TYPES)[number];
61
+ declare const AVATAR_REJECTION: {
62
+ readonly TOO_LARGE: "avatar_too_large";
63
+ readonly UNSUPPORTED_TYPE: "avatar_unsupported_type";
64
+ readonly EMPTY: "avatar_empty";
65
+ };
66
+ type AvatarRejection = (typeof AVATAR_REJECTION)[keyof typeof AVATAR_REJECTION];
67
+ type CheckAvatarParams = {
68
+ readonly contentType: string;
69
+ readonly byteLength: number;
70
+ };
71
+ /**
72
+ * Devolve o motivo, e não um booleano: quem chama precisa dizer à pessoa se o arquivo é grande
73
+ * demais ou se é do tipo errado — são duas correções diferentes.
74
+ */
75
+ declare function checkAvatar(params: CheckAvatarParams): AvatarRejection | undefined;
76
+ type PutAvatarParams = {
77
+ readonly userId: string;
78
+ readonly body: Uint8Array;
79
+ readonly contentType: AvatarContentType;
80
+ };
81
+ /**
82
+ * O host pluga o armazenamento; o módulo não sabe se é S3, disco ou memória.
83
+ *
84
+ * Sem esta porta o módulo não publica as rotas de foto — capacidade por ausência. Um produto sem
85
+ * bucket não tem uma foto quebrada: não tem foto.
86
+ */
87
+ type AvatarStoragePort = {
88
+ /** Grava e devolve a chave opaca a guardar na linha do usuário. */
89
+ put(params: PutAvatarParams): Promise<string>;
90
+ /**
91
+ * URL de leitura de vida curta.
92
+ *
93
+ * Assinada, e não pública: um rosto de funcionário não é o logo da empresa, e bucket aberto
94
+ * indexa. Curta porque ela viaja na resposta da listagem, que passa por log e por cache.
95
+ */
96
+ sign(key: string): Promise<string>;
97
+ /** Remove a foto anterior; falhar aqui não pode derrubar a troca (o lixo é varrido depois). */
98
+ remove?(key: string): Promise<void>;
99
+ };
100
+
41
101
  /**
42
102
  * Copyright (c) 2026 Ada Technology. All rights reserved.
43
103
  *
@@ -57,6 +117,7 @@ declare const USER_ERROR_CODE: {
57
117
  readonly PROVIDER_MISCONFIGURED: "USER_PROVIDER_MISCONFIGURED";
58
118
  readonly PROVIDER_DISABLED: "USER_PROVIDER_DISABLED";
59
119
  readonly CONFIG_MISSING: "USER_CONFIG_MISSING";
120
+ readonly AVATAR_REJECTED: "USER_AVATAR_REJECTED";
60
121
  };
61
122
  type UserErrorCode = (typeof USER_ERROR_CODE)[keyof typeof USER_ERROR_CODE];
62
123
  declare class UserError extends Error {
@@ -103,6 +164,15 @@ declare class ProviderDisabledError extends UserError {
103
164
  declare class ConfigMissingError extends UserError {
104
165
  constructor(field: string);
105
166
  }
167
+ /**
168
+ * O `details.reason` e o motivo estavel (`AvatarRejection`), nao a frase.
169
+ *
170
+ * "Grande demais" e "tipo nao suportado" pedem correcoes diferentes, e quem monta a tela precisa
171
+ * distinguir os dois sem casar string traduzida.
172
+ */
173
+ declare class AvatarRejectedError extends UserError {
174
+ constructor(reason: string);
175
+ }
106
176
 
107
177
  /**
108
178
  * Copyright (c) 2026 Ada Technology. All rights reserved.
@@ -239,16 +309,42 @@ type RefreshTokenStorePort = {
239
309
  readonly userId: string;
240
310
  }): Promise<void>;
241
311
  };
312
+ /**
313
+ * Forma **idêntica** à de `@adatechnology/notification-contracts` — redeclarada, e não importada,
314
+ * só para um pacote de contratos não arrastar outro domínio de runtime junto. Como o TypeScript é
315
+ * estrutural, qualquer driver de `@adatechnology/email-provider` (`createSmtpEmailProvider`,
316
+ * `createResendEmailProvider`, `createSesEmailProvider`) entra direto em `providers.email`, sem
317
+ * adapter no host.
318
+ *
319
+ * Divergir daqui é o que quebra essa troca: a redeclaração só serve se as duas formas forem a
320
+ * mesma, e nada no build de um pacote isolado avisa quando deixam de ser.
321
+ */
242
322
  type SendEmailParams = {
243
323
  readonly to: string;
244
324
  readonly subject: string;
245
- readonly body: string;
246
- readonly html?: string;
325
+ readonly html: string;
326
+ readonly text: string;
327
+ readonly replyTo?: string;
328
+ readonly idempotencyKey?: string;
247
329
  };
330
+ /**
331
+ * União discriminada, não `{ success: boolean }`: quem chama precisa separar endereço inválido
332
+ * (suprimir, nunca reenviar) de falha temporária (reagendar com backoff) de falha definitiva.
333
+ * Colapsar isso num booleano joga fora justamente a informação que decide a ação seguinte.
334
+ */
248
335
  type DeliveryAttemptResult = {
249
- readonly success: boolean;
250
- readonly messageId?: string;
251
- readonly error?: string;
336
+ readonly outcome: 'sent';
337
+ readonly providerMessageId?: string;
338
+ } | {
339
+ readonly outcome: 'invalid_target';
340
+ readonly errorCode: string;
341
+ } | {
342
+ readonly outcome: 'retriable';
343
+ readonly errorCode: string;
344
+ readonly retryAfterSeconds?: number;
345
+ } | {
346
+ readonly outcome: 'permanent';
347
+ readonly errorCode: string;
252
348
  };
253
349
  type EmailDriverPort = {
254
350
  readonly driver: string;
@@ -286,9 +382,26 @@ type AccessTokenConfig = {
286
382
  type RefreshTokenConfig = {
287
383
  readonly expiresInSeconds?: number;
288
384
  };
385
+ type PasswordResetEmailContent = {
386
+ readonly subject: string;
387
+ readonly html: string;
388
+ readonly text: string;
389
+ };
390
+ type PasswordResetEmailParams = {
391
+ readonly resetUrl: string;
392
+ readonly name: string;
393
+ readonly expiresInSeconds: number;
394
+ };
289
395
  type PasswordResetConfig = {
290
396
  readonly resetUrlTemplate: string;
291
397
  readonly tokenExpiresInSeconds?: number;
398
+ /**
399
+ * Texto do e-mail de redefinição. O módulo traz um padrão neutro, sem nome de produto e sem
400
+ * marca — copy é vocabulário do host (`pluggable-module.md`), e cinco produtos consomem este
401
+ * pacote. Quem quiser template versionado e pré-visualizável monta aqui em cima de
402
+ * `renderTemplate` do `@adatechnology/notification-contracts`.
403
+ */
404
+ readonly buildEmail?: (params: PasswordResetEmailParams) => PasswordResetEmailContent;
292
405
  };
293
406
  type KeycloakConfig = {
294
407
  readonly realm: string;
@@ -392,4 +505,4 @@ declare const keycloakCallbackSchema: z.ZodObject<{
392
505
  }>;
393
506
  type KeycloakCallbackInput = z.infer<typeof keycloakCallbackSchema>;
394
507
 
395
- export { AUTH_PROVIDER_TYPE, type AccessTokenConfig, type AttributeMapping, type AttributeMappingRule, type AuthProviderInterface, type AuthProviderType, type BaseEvent, type ClockPort, ConfigMissingError, type ConfirmPasswordResetInput, type CreateUserInput, type DeliveryAttemptResult, EmailAlreadyExistsError, type EmailDriverPort, InvalidCredentialsError, type KeycloakCallbackInput, type KeycloakConfig, type ListUsersParams, type LocalCredentials, type LogMeta, type LoggerPort, type LoginFailedEvent, type LoginSucceededEvent, NotAuthenticatedError, type PaginatedResponse, type PasswordChangedEvent, type PasswordResetCompletedEvent, type PasswordResetConfig, type PasswordResetRequestedEvent, type ProfileUpdatedEvent, ProviderDisabledError, ProviderMisconfiguredError, type RefreshTokenConfig, type RefreshTokenStorePort, type RequestPasswordResetInput, ResetTokenAlreadyUsedError, ResetTokenExpiredError, ResetTokenInvalidError, type SendEmailParams, type SessionStatus, type TenancyConfig, USER_ERROR_CODE, USER_EVENT, type UpdateProfileInput, type UserCreatedEvent, type UserDomainEvent, UserError, type UserErrorCode, type UserEventType, type UserHooks, type UserModuleConfig, type UserModuleProviders, UserNotFoundError, type UserProfile, type UserSession, type UserUpdatedEvent, WeakPasswordError, confirmPasswordResetSchema, createUserSchema, keycloakCallbackSchema, localCredentialsSchema, requestPasswordResetSchema, updateProfileSchema };
508
+ export { AUTH_PROVIDER_TYPE, AVATAR_CONTENT_TYPES, AVATAR_MAX_BYTES, AVATAR_REJECTION, type AccessTokenConfig, type AttributeMapping, type AttributeMappingRule, type AuthProviderInterface, type AuthProviderType, type AvatarContentType, AvatarRejectedError, type AvatarRejection, type AvatarStoragePort, type BaseEvent, type CheckAvatarParams, type ClockPort, ConfigMissingError, type ConfirmPasswordResetInput, type CreateUserInput, type DeliveryAttemptResult, EmailAlreadyExistsError, type EmailDriverPort, InvalidCredentialsError, type KeycloakCallbackInput, type KeycloakConfig, type ListUsersParams, type LocalCredentials, type LogMeta, type LoggerPort, type LoginFailedEvent, type LoginSucceededEvent, NotAuthenticatedError, type PaginatedResponse, type PasswordChangedEvent, type PasswordResetCompletedEvent, type PasswordResetConfig, type PasswordResetEmailContent, type PasswordResetEmailParams, type PasswordResetRequestedEvent, type ProfileUpdatedEvent, ProviderDisabledError, ProviderMisconfiguredError, type PutAvatarParams, type RefreshTokenConfig, type RefreshTokenStorePort, type RequestPasswordResetInput, ResetTokenAlreadyUsedError, ResetTokenExpiredError, ResetTokenInvalidError, type SendEmailParams, type SessionStatus, type TenancyConfig, USER_ERROR_CODE, USER_EVENT, type UpdateProfileInput, type UserCreatedEvent, type UserDomainEvent, UserError, type UserErrorCode, type UserEventType, type UserHooks, type UserModuleConfig, type UserModuleProviders, UserNotFoundError, type UserProfile, type UserSession, type UserUpdatedEvent, WeakPasswordError, checkAvatar, confirmPasswordResetSchema, createUserSchema, keycloakCallbackSchema, localCredentialsSchema, requestPasswordResetSchema, updateProfileSchema };
package/dist/index.d.ts CHANGED
@@ -15,6 +15,13 @@ type UserProfile = {
15
15
  readonly companyId?: string;
16
16
  readonly isActive: boolean;
17
17
  readonly lastSeenAt?: Date;
18
+ /**
19
+ * URL de leitura assinada, presente so quando ha foto E o host plugou armazenamento.
20
+ *
21
+ * Ausente cobre os dois casos porque a tela faz a mesma coisa nos dois: desenha as iniciais. Um
22
+ * campo separado dizendo "tem foto, mas nao consigo servir" nao mudaria um pixel.
23
+ */
24
+ readonly avatarUrl?: string;
18
25
  };
19
26
  type UserSession = {
20
27
  readonly accessToken: string;
@@ -38,6 +45,59 @@ type ListUsersParams = {
38
45
  readonly companyId?: string;
39
46
  };
40
47
 
48
+ /**
49
+ * Copyright (c) 2026 Ada Technology. MIT License.
50
+ *
51
+ * A foto de perfil: porta de armazenamento e a validação que roda antes de qualquer rede.
52
+ */
53
+ /** 2 MB. Foto de perfil é exibida em 40px numa tabela; o que passa disso é desperdício de banda. */
54
+ declare const AVATAR_MAX_BYTES: number;
55
+ /**
56
+ * Lista fechada, e não `image/*`: `image/svg+xml` é um documento com script dentro, e servido do
57
+ * mesmo domínio viraria XSS. Nenhum destes três executa nada.
58
+ */
59
+ declare const AVATAR_CONTENT_TYPES: readonly ["image/jpeg", "image/png", "image/webp"];
60
+ type AvatarContentType = (typeof AVATAR_CONTENT_TYPES)[number];
61
+ declare const AVATAR_REJECTION: {
62
+ readonly TOO_LARGE: "avatar_too_large";
63
+ readonly UNSUPPORTED_TYPE: "avatar_unsupported_type";
64
+ readonly EMPTY: "avatar_empty";
65
+ };
66
+ type AvatarRejection = (typeof AVATAR_REJECTION)[keyof typeof AVATAR_REJECTION];
67
+ type CheckAvatarParams = {
68
+ readonly contentType: string;
69
+ readonly byteLength: number;
70
+ };
71
+ /**
72
+ * Devolve o motivo, e não um booleano: quem chama precisa dizer à pessoa se o arquivo é grande
73
+ * demais ou se é do tipo errado — são duas correções diferentes.
74
+ */
75
+ declare function checkAvatar(params: CheckAvatarParams): AvatarRejection | undefined;
76
+ type PutAvatarParams = {
77
+ readonly userId: string;
78
+ readonly body: Uint8Array;
79
+ readonly contentType: AvatarContentType;
80
+ };
81
+ /**
82
+ * O host pluga o armazenamento; o módulo não sabe se é S3, disco ou memória.
83
+ *
84
+ * Sem esta porta o módulo não publica as rotas de foto — capacidade por ausência. Um produto sem
85
+ * bucket não tem uma foto quebrada: não tem foto.
86
+ */
87
+ type AvatarStoragePort = {
88
+ /** Grava e devolve a chave opaca a guardar na linha do usuário. */
89
+ put(params: PutAvatarParams): Promise<string>;
90
+ /**
91
+ * URL de leitura de vida curta.
92
+ *
93
+ * Assinada, e não pública: um rosto de funcionário não é o logo da empresa, e bucket aberto
94
+ * indexa. Curta porque ela viaja na resposta da listagem, que passa por log e por cache.
95
+ */
96
+ sign(key: string): Promise<string>;
97
+ /** Remove a foto anterior; falhar aqui não pode derrubar a troca (o lixo é varrido depois). */
98
+ remove?(key: string): Promise<void>;
99
+ };
100
+
41
101
  /**
42
102
  * Copyright (c) 2026 Ada Technology. All rights reserved.
43
103
  *
@@ -57,6 +117,7 @@ declare const USER_ERROR_CODE: {
57
117
  readonly PROVIDER_MISCONFIGURED: "USER_PROVIDER_MISCONFIGURED";
58
118
  readonly PROVIDER_DISABLED: "USER_PROVIDER_DISABLED";
59
119
  readonly CONFIG_MISSING: "USER_CONFIG_MISSING";
120
+ readonly AVATAR_REJECTED: "USER_AVATAR_REJECTED";
60
121
  };
61
122
  type UserErrorCode = (typeof USER_ERROR_CODE)[keyof typeof USER_ERROR_CODE];
62
123
  declare class UserError extends Error {
@@ -103,6 +164,15 @@ declare class ProviderDisabledError extends UserError {
103
164
  declare class ConfigMissingError extends UserError {
104
165
  constructor(field: string);
105
166
  }
167
+ /**
168
+ * O `details.reason` e o motivo estavel (`AvatarRejection`), nao a frase.
169
+ *
170
+ * "Grande demais" e "tipo nao suportado" pedem correcoes diferentes, e quem monta a tela precisa
171
+ * distinguir os dois sem casar string traduzida.
172
+ */
173
+ declare class AvatarRejectedError extends UserError {
174
+ constructor(reason: string);
175
+ }
106
176
 
107
177
  /**
108
178
  * Copyright (c) 2026 Ada Technology. All rights reserved.
@@ -239,16 +309,42 @@ type RefreshTokenStorePort = {
239
309
  readonly userId: string;
240
310
  }): Promise<void>;
241
311
  };
312
+ /**
313
+ * Forma **idêntica** à de `@adatechnology/notification-contracts` — redeclarada, e não importada,
314
+ * só para um pacote de contratos não arrastar outro domínio de runtime junto. Como o TypeScript é
315
+ * estrutural, qualquer driver de `@adatechnology/email-provider` (`createSmtpEmailProvider`,
316
+ * `createResendEmailProvider`, `createSesEmailProvider`) entra direto em `providers.email`, sem
317
+ * adapter no host.
318
+ *
319
+ * Divergir daqui é o que quebra essa troca: a redeclaração só serve se as duas formas forem a
320
+ * mesma, e nada no build de um pacote isolado avisa quando deixam de ser.
321
+ */
242
322
  type SendEmailParams = {
243
323
  readonly to: string;
244
324
  readonly subject: string;
245
- readonly body: string;
246
- readonly html?: string;
325
+ readonly html: string;
326
+ readonly text: string;
327
+ readonly replyTo?: string;
328
+ readonly idempotencyKey?: string;
247
329
  };
330
+ /**
331
+ * União discriminada, não `{ success: boolean }`: quem chama precisa separar endereço inválido
332
+ * (suprimir, nunca reenviar) de falha temporária (reagendar com backoff) de falha definitiva.
333
+ * Colapsar isso num booleano joga fora justamente a informação que decide a ação seguinte.
334
+ */
248
335
  type DeliveryAttemptResult = {
249
- readonly success: boolean;
250
- readonly messageId?: string;
251
- readonly error?: string;
336
+ readonly outcome: 'sent';
337
+ readonly providerMessageId?: string;
338
+ } | {
339
+ readonly outcome: 'invalid_target';
340
+ readonly errorCode: string;
341
+ } | {
342
+ readonly outcome: 'retriable';
343
+ readonly errorCode: string;
344
+ readonly retryAfterSeconds?: number;
345
+ } | {
346
+ readonly outcome: 'permanent';
347
+ readonly errorCode: string;
252
348
  };
253
349
  type EmailDriverPort = {
254
350
  readonly driver: string;
@@ -286,9 +382,26 @@ type AccessTokenConfig = {
286
382
  type RefreshTokenConfig = {
287
383
  readonly expiresInSeconds?: number;
288
384
  };
385
+ type PasswordResetEmailContent = {
386
+ readonly subject: string;
387
+ readonly html: string;
388
+ readonly text: string;
389
+ };
390
+ type PasswordResetEmailParams = {
391
+ readonly resetUrl: string;
392
+ readonly name: string;
393
+ readonly expiresInSeconds: number;
394
+ };
289
395
  type PasswordResetConfig = {
290
396
  readonly resetUrlTemplate: string;
291
397
  readonly tokenExpiresInSeconds?: number;
398
+ /**
399
+ * Texto do e-mail de redefinição. O módulo traz um padrão neutro, sem nome de produto e sem
400
+ * marca — copy é vocabulário do host (`pluggable-module.md`), e cinco produtos consomem este
401
+ * pacote. Quem quiser template versionado e pré-visualizável monta aqui em cima de
402
+ * `renderTemplate` do `@adatechnology/notification-contracts`.
403
+ */
404
+ readonly buildEmail?: (params: PasswordResetEmailParams) => PasswordResetEmailContent;
292
405
  };
293
406
  type KeycloakConfig = {
294
407
  readonly realm: string;
@@ -392,4 +505,4 @@ declare const keycloakCallbackSchema: z.ZodObject<{
392
505
  }>;
393
506
  type KeycloakCallbackInput = z.infer<typeof keycloakCallbackSchema>;
394
507
 
395
- export { AUTH_PROVIDER_TYPE, type AccessTokenConfig, type AttributeMapping, type AttributeMappingRule, type AuthProviderInterface, type AuthProviderType, type BaseEvent, type ClockPort, ConfigMissingError, type ConfirmPasswordResetInput, type CreateUserInput, type DeliveryAttemptResult, EmailAlreadyExistsError, type EmailDriverPort, InvalidCredentialsError, type KeycloakCallbackInput, type KeycloakConfig, type ListUsersParams, type LocalCredentials, type LogMeta, type LoggerPort, type LoginFailedEvent, type LoginSucceededEvent, NotAuthenticatedError, type PaginatedResponse, type PasswordChangedEvent, type PasswordResetCompletedEvent, type PasswordResetConfig, type PasswordResetRequestedEvent, type ProfileUpdatedEvent, ProviderDisabledError, ProviderMisconfiguredError, type RefreshTokenConfig, type RefreshTokenStorePort, type RequestPasswordResetInput, ResetTokenAlreadyUsedError, ResetTokenExpiredError, ResetTokenInvalidError, type SendEmailParams, type SessionStatus, type TenancyConfig, USER_ERROR_CODE, USER_EVENT, type UpdateProfileInput, type UserCreatedEvent, type UserDomainEvent, UserError, type UserErrorCode, type UserEventType, type UserHooks, type UserModuleConfig, type UserModuleProviders, UserNotFoundError, type UserProfile, type UserSession, type UserUpdatedEvent, WeakPasswordError, confirmPasswordResetSchema, createUserSchema, keycloakCallbackSchema, localCredentialsSchema, requestPasswordResetSchema, updateProfileSchema };
508
+ export { AUTH_PROVIDER_TYPE, AVATAR_CONTENT_TYPES, AVATAR_MAX_BYTES, AVATAR_REJECTION, type AccessTokenConfig, type AttributeMapping, type AttributeMappingRule, type AuthProviderInterface, type AuthProviderType, type AvatarContentType, AvatarRejectedError, type AvatarRejection, type AvatarStoragePort, type BaseEvent, type CheckAvatarParams, type ClockPort, ConfigMissingError, type ConfirmPasswordResetInput, type CreateUserInput, type DeliveryAttemptResult, EmailAlreadyExistsError, type EmailDriverPort, InvalidCredentialsError, type KeycloakCallbackInput, type KeycloakConfig, type ListUsersParams, type LocalCredentials, type LogMeta, type LoggerPort, type LoginFailedEvent, type LoginSucceededEvent, NotAuthenticatedError, type PaginatedResponse, type PasswordChangedEvent, type PasswordResetCompletedEvent, type PasswordResetConfig, type PasswordResetEmailContent, type PasswordResetEmailParams, type PasswordResetRequestedEvent, type ProfileUpdatedEvent, ProviderDisabledError, ProviderMisconfiguredError, type PutAvatarParams, type RefreshTokenConfig, type RefreshTokenStorePort, type RequestPasswordResetInput, ResetTokenAlreadyUsedError, ResetTokenExpiredError, ResetTokenInvalidError, type SendEmailParams, type SessionStatus, type TenancyConfig, USER_ERROR_CODE, USER_EVENT, type UpdateProfileInput, type UserCreatedEvent, type UserDomainEvent, UserError, type UserErrorCode, type UserEventType, type UserHooks, type UserModuleConfig, type UserModuleProviders, UserNotFoundError, type UserProfile, type UserSession, type UserUpdatedEvent, WeakPasswordError, checkAvatar, confirmPasswordResetSchema, createUserSchema, keycloakCallbackSchema, localCredentialsSchema, requestPasswordResetSchema, updateProfileSchema };
package/dist/index.js CHANGED
@@ -1,6 +1,28 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
+ // src/avatar.ts
5
+ var AVATAR_MAX_BYTES = 2 * 1024 * 1024;
6
+ var AVATAR_CONTENT_TYPES = [
7
+ "image/jpeg",
8
+ "image/png",
9
+ "image/webp"
10
+ ];
11
+ var AVATAR_REJECTION = {
12
+ TOO_LARGE: "avatar_too_large",
13
+ UNSUPPORTED_TYPE: "avatar_unsupported_type",
14
+ EMPTY: "avatar_empty"
15
+ };
16
+ function checkAvatar(params) {
17
+ if (params.byteLength <= 0) return AVATAR_REJECTION.EMPTY;
18
+ if (params.byteLength > AVATAR_MAX_BYTES) return AVATAR_REJECTION.TOO_LARGE;
19
+ if (!AVATAR_CONTENT_TYPES.includes(params.contentType)) {
20
+ return AVATAR_REJECTION.UNSUPPORTED_TYPE;
21
+ }
22
+ return void 0;
23
+ }
24
+ __name(checkAvatar, "checkAvatar");
25
+
4
26
  // src/errors.ts
5
27
  var USER_ERROR_CODE = {
6
28
  INVALID_CREDENTIALS: "USER_INVALID_CREDENTIALS",
@@ -13,7 +35,8 @@ var USER_ERROR_CODE = {
13
35
  WEAK_PASSWORD: "USER_WEAK_PASSWORD",
14
36
  PROVIDER_MISCONFIGURED: "USER_PROVIDER_MISCONFIGURED",
15
37
  PROVIDER_DISABLED: "USER_PROVIDER_DISABLED",
16
- CONFIG_MISSING: "USER_CONFIG_MISSING"
38
+ CONFIG_MISSING: "USER_CONFIG_MISSING",
39
+ AVATAR_REJECTED: "USER_AVATAR_REJECTED"
17
40
  };
18
41
  var UserError = class extends Error {
19
42
  static {
@@ -177,6 +200,22 @@ var ConfigMissingError = class extends UserError {
177
200
  this.name = "ConfigMissingError";
178
201
  }
179
202
  };
203
+ var AvatarRejectedError = class extends UserError {
204
+ static {
205
+ __name(this, "AvatarRejectedError");
206
+ }
207
+ constructor(reason) {
208
+ super({
209
+ message: "Avatar rejected",
210
+ statusCode: 400,
211
+ code: USER_ERROR_CODE.AVATAR_REJECTED,
212
+ details: {
213
+ reason
214
+ }
215
+ });
216
+ this.name = "AvatarRejectedError";
217
+ }
218
+ };
180
219
 
181
220
  // src/events.ts
182
221
  var USER_EVENT = {
@@ -226,6 +265,10 @@ var keycloakCallbackSchema = z.object({
226
265
  });
227
266
  export {
228
267
  AUTH_PROVIDER_TYPE,
268
+ AVATAR_CONTENT_TYPES,
269
+ AVATAR_MAX_BYTES,
270
+ AVATAR_REJECTION,
271
+ AvatarRejectedError,
229
272
  ConfigMissingError,
230
273
  EmailAlreadyExistsError,
231
274
  InvalidCredentialsError,
@@ -240,6 +283,7 @@ export {
240
283
  UserError,
241
284
  UserNotFoundError,
242
285
  WeakPasswordError,
286
+ checkAvatar,
243
287
  confirmPasswordResetSchema,
244
288
  createUserSchema,
245
289
  keycloakCallbackSchema,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/events.ts","../src/providers.ts","../src/schemas.ts"],"sourcesContent":["/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nexport const USER_ERROR_CODE = {\n INVALID_CREDENTIALS: 'USER_INVALID_CREDENTIALS',\n USER_NOT_FOUND: 'USER_NOT_FOUND',\n EMAIL_ALREADY_EXISTS: 'USER_EMAIL_ALREADY_EXISTS',\n NOT_AUTHENTICATED: 'USER_NOT_AUTHENTICATED',\n RESET_TOKEN_INVALID: 'USER_RESET_TOKEN_INVALID',\n RESET_TOKEN_EXPIRED: 'USER_RESET_TOKEN_EXPIRED',\n RESET_TOKEN_ALREADY_USED: 'USER_RESET_TOKEN_ALREADY_USED',\n WEAK_PASSWORD: 'USER_WEAK_PASSWORD',\n PROVIDER_MISCONFIGURED: 'USER_PROVIDER_MISCONFIGURED',\n PROVIDER_DISABLED: 'USER_PROVIDER_DISABLED',\n CONFIG_MISSING: 'USER_CONFIG_MISSING',\n} as const\n\nexport type UserErrorCode = (typeof USER_ERROR_CODE)[keyof typeof USER_ERROR_CODE]\n\nexport class UserError extends Error {\n readonly statusCode: number\n readonly code: UserErrorCode\n readonly details?: unknown\n\n constructor(params: { message: string; statusCode: number; code: UserErrorCode; details?: unknown }) {\n super(params.message)\n this.name = 'UserError'\n this.statusCode = params.statusCode\n this.code = params.code\n this.details = params.details\n }\n}\n\nexport class InvalidCredentialsError extends UserError {\n constructor() {\n super({\n message: 'Invalid credentials',\n statusCode: 401,\n code: USER_ERROR_CODE.INVALID_CREDENTIALS,\n })\n this.name = 'InvalidCredentialsError'\n }\n}\n\nexport class UserNotFoundError extends UserError {\n constructor() {\n super({\n message: 'User not found',\n statusCode: 404,\n code: USER_ERROR_CODE.USER_NOT_FOUND,\n })\n this.name = 'UserNotFoundError'\n }\n}\n\nexport class EmailAlreadyExistsError extends UserError {\n constructor() {\n super({\n message: 'Email already exists',\n statusCode: 409,\n code: USER_ERROR_CODE.EMAIL_ALREADY_EXISTS,\n })\n this.name = 'EmailAlreadyExistsError'\n }\n}\n\nexport class NotAuthenticatedError extends UserError {\n constructor() {\n super({\n message: 'Not authenticated',\n statusCode: 401,\n code: USER_ERROR_CODE.NOT_AUTHENTICATED,\n })\n this.name = 'NotAuthenticatedError'\n }\n}\n\nexport class ResetTokenInvalidError extends UserError {\n constructor() {\n super({\n message: 'Invalid reset token',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_INVALID,\n })\n this.name = 'ResetTokenInvalidError'\n }\n}\n\nexport class ResetTokenExpiredError extends UserError {\n constructor() {\n super({\n message: 'Reset token expired',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_EXPIRED,\n })\n this.name = 'ResetTokenExpiredError'\n }\n}\n\nexport class ResetTokenAlreadyUsedError extends UserError {\n constructor() {\n super({\n message: 'Reset token already used',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_ALREADY_USED,\n })\n this.name = 'ResetTokenAlreadyUsedError'\n }\n}\n\nexport class WeakPasswordError extends UserError {\n constructor() {\n super({\n message: 'Password does not meet security requirements',\n statusCode: 400,\n code: USER_ERROR_CODE.WEAK_PASSWORD,\n })\n this.name = 'WeakPasswordError'\n }\n}\n\nexport class ProviderMisconfiguredError extends UserError {\n constructor(details?: unknown) {\n super({\n message: 'Authentication provider is misconfigured',\n statusCode: 500,\n code: USER_ERROR_CODE.PROVIDER_MISCONFIGURED,\n details,\n })\n this.name = 'ProviderMisconfiguredError'\n }\n}\n\nexport class ProviderDisabledError extends UserError {\n constructor() {\n super({\n message: 'Authentication provider is not available',\n statusCode: 503,\n code: USER_ERROR_CODE.PROVIDER_DISABLED,\n })\n this.name = 'ProviderDisabledError'\n }\n}\n\nexport class ConfigMissingError extends UserError {\n constructor(field: string) {\n super({\n message: `Missing required configuration: ${field}`,\n statusCode: 500,\n code: USER_ERROR_CODE.CONFIG_MISSING,\n details: { field },\n })\n this.name = 'ConfigMissingError'\n }\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport type { UserProfile } from './user.types'\n\nexport const USER_EVENT = {\n USER_CREATED: 'user.user.created',\n USER_UPDATED: 'user.user.updated',\n LOGIN_SUCCEEDED: 'user.login.succeeded',\n LOGIN_FAILED: 'user.login.failed',\n PASSWORD_CHANGED: 'user.password.changed',\n PASSWORD_RESET_REQUESTED: 'user.password_reset.requested',\n PASSWORD_RESET_COMPLETED: 'user.password_reset.completed',\n PROFILE_UPDATED: 'user.profile.updated',\n} as const\n\nexport type UserEventType = (typeof USER_EVENT)[keyof typeof USER_EVENT]\n\nexport type BaseEvent = {\n readonly companyId?: string\n readonly occurredAt: Date\n}\n\nexport type UserCreatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.USER_CREATED\n readonly userId: string\n readonly email: string\n}\n\nexport type UserUpdatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.USER_UPDATED\n readonly userId: string\n readonly user: UserProfile\n}\n\nexport type LoginSucceededEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.LOGIN_SUCCEEDED\n readonly userId: string\n readonly email: string\n readonly ipAddress: string\n}\n\nexport type LoginFailedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.LOGIN_FAILED\n readonly email: string\n readonly ipAddress: string\n readonly reason: string\n}\n\nexport type PasswordChangedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_CHANGED\n readonly userId: string\n}\n\nexport type PasswordResetRequestedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_RESET_REQUESTED\n readonly email: string\n readonly resetUrl: string\n}\n\nexport type PasswordResetCompletedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_RESET_COMPLETED\n readonly userId: string\n readonly email: string\n}\n\nexport type ProfileUpdatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PROFILE_UPDATED\n readonly userId: string\n readonly user: UserProfile\n}\n\nexport type UserDomainEvent =\n | UserCreatedEvent\n | UserUpdatedEvent\n | LoginSucceededEvent\n | LoginFailedEvent\n | PasswordChangedEvent\n | PasswordResetRequestedEvent\n | PasswordResetCompletedEvent\n | ProfileUpdatedEvent\n\nexport type UserHooks = {\n readonly onUserCreated?: (event: UserCreatedEvent) => Promise<void> | void\n readonly onUserUpdated?: (event: UserUpdatedEvent) => Promise<void> | void\n readonly onLoginSucceeded?: (event: LoginSucceededEvent) => Promise<void> | void\n readonly onLoginFailed?: (event: LoginFailedEvent) => Promise<void> | void\n readonly onPasswordChanged?: (event: PasswordChangedEvent) => Promise<void> | void\n readonly onPasswordResetRequested?: (event: PasswordResetRequestedEvent) => Promise<void> | void\n readonly onPasswordResetCompleted?: (event: PasswordResetCompletedEvent) => Promise<void> | void\n readonly onProfileUpdated?: (event: ProfileUpdatedEvent) => Promise<void> | void\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport type { UserSession } from './user.types'\n\nexport const AUTH_PROVIDER_TYPE = {\n LOCAL: 'local',\n OAUTH2: 'oauth2',\n OIDC: 'oidc',\n} as const\n\nexport type AuthProviderType = (typeof AUTH_PROVIDER_TYPE)[keyof typeof AUTH_PROVIDER_TYPE]\n\nexport type AuthProviderInterface<TCredentials = unknown> = {\n readonly id: string\n readonly type: AuthProviderType\n authenticate(params: { readonly credentials: TCredentials; readonly ipAddress?: string }): Promise<UserSession>\n}\n\nexport type AttributeMappingRule<TClaims extends Record<string, unknown>> =\n | { readonly from: keyof TClaims }\n | { readonly value: string }\n\nexport type AttributeMapping<TClaims extends Record<string, unknown>> = {\n readonly email: AttributeMappingRule<TClaims>\n readonly name?: AttributeMappingRule<TClaims>\n readonly role?: AttributeMappingRule<TClaims>\n}\n\n/**\n * `issue` devolve o token cru; `rotate`/`revoke` recebem o **sha256 hex** dele — o token cru nunca\n * chega ao armazenamento, então um dump do Redis ou da tabela não vale sessão.\n *\n * `revokeAllForUser` é o que faz uma troca de senha realmente encerrar as sessões abertas. Sem ela,\n * a sessão comprometida sobrevive à redefinição feita justamente para matá-la — por isso é\n * obrigatória no contrato, e não uma capacidade opcional.\n */\nexport type RefreshTokenStorePort = {\n issue(params: { readonly userId: string; readonly expiresInSeconds: number }): Promise<string>\n rotate(params: {\n readonly tokenHash: string\n readonly newExpiresInSeconds: number\n }): Promise<{ readonly token: string; readonly userId: string } | null>\n revoke(params: { readonly tokenHash: string }): Promise<void>\n revokeAllForUser(params: { readonly userId: string }): Promise<void>\n}\n\nexport type SendEmailParams = {\n readonly to: string\n readonly subject: string\n readonly body: string\n readonly html?: string\n}\n\nexport type DeliveryAttemptResult = {\n readonly success: boolean\n readonly messageId?: string\n readonly error?: string\n}\n\nexport type EmailDriverPort = {\n readonly driver: string\n send(params: SendEmailParams): Promise<DeliveryAttemptResult>\n}\n\nexport type ClockPort = {\n now(): Date\n}\n\nexport type LogMeta = Readonly<Record<string, unknown>>\n\n/** Mesma forma do `LoggerPort` dos outros contratos do ecossistema — o host escreve um adapter só. */\nexport type LoggerPort = {\n error(message: string, meta?: LogMeta): void\n warn(message: string, meta?: LogMeta): void\n info(message: string, meta?: LogMeta): void\n debug(message: string, meta?: LogMeta): void\n}\n\nexport type TenancyConfig = { readonly mode: 'single'; readonly defaultCompanyId: string } | { readonly mode: 'multi' }\n\n/**\n * `issuer`/`audience` são opcionais, mas quando declarados valem na assinatura **e** na\n * verificação: um token emitido para outra plateia é recusado. Um host que já emite JWT por conta\n * própria (migração gradual, dois emissores sobre o mesmo segredo) precisa declarar os mesmos\n * valores aqui, ou os dois lados não reconhecem o token um do outro.\n */\nexport type AccessTokenConfig = {\n readonly secret: string\n readonly expiresInSeconds?: number\n readonly issuer?: string\n readonly audience?: string\n}\n\nexport type RefreshTokenConfig = {\n readonly expiresInSeconds?: number\n}\n\nexport type PasswordResetConfig = {\n readonly resetUrlTemplate: string // must contain {token}\n readonly tokenExpiresInSeconds?: number\n}\n\nexport type KeycloakConfig = {\n readonly realm: string\n readonly authServerUrl: string\n readonly clientId: string\n readonly clientSecret?: string\n readonly attributeMapping?: AttributeMapping<Record<string, unknown>>\n}\n\nexport type UserModuleConfig = {\n readonly tenancy: TenancyConfig\n readonly accessToken: AccessTokenConfig\n readonly refreshToken?: RefreshTokenConfig\n readonly passwordReset?: PasswordResetConfig\n readonly keycloak?: KeycloakConfig\n}\n\n/**\n * `keycloak` não entra aqui: a verificação de token é plumbing específico do módulo (ver\n * `KeycloakVerifierPort` em `user-module`), não um contrato genérico reutilizável por outro host.\n */\nexport type UserModuleProviders = {\n readonly refreshTokenStore?: RefreshTokenStorePort\n readonly email?: EmailDriverPort\n readonly clock?: ClockPort\n readonly logger?: LoggerPort\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport { z } from 'zod'\n\nconst PASSWORD_MIN_LENGTH = 8\nconst PASSWORD_MAX_LENGTH = 128\n\nexport const localCredentialsSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n password: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n})\n\nexport type LocalCredentials = z.infer<typeof localCredentialsSchema>\n\nexport const requestPasswordResetSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n})\n\nexport type RequestPasswordResetInput = z.infer<typeof requestPasswordResetSchema>\n\nexport const confirmPasswordResetSchema = z.object({\n token: z.string().min(1),\n newPassword: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n})\n\nexport type ConfirmPasswordResetInput = z.infer<typeof confirmPasswordResetSchema>\n\nexport const updateProfileSchema = z.object({\n name: z.string().trim().min(1).max(255).optional(),\n})\n\nexport type UpdateProfileInput = z.infer<typeof updateProfileSchema>\n\nexport const createUserSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n name: z.string().trim().min(1).max(255),\n password: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n role: z.string().min(1).max(40),\n})\n\nexport type CreateUserInput = z.infer<typeof createUserSchema>\n\n/**\n * `accessToken`, não `code`/`state`: o módulo verifica localmente um token já emitido pelo\n * Keycloak (`@adatechnology/auth-keycloak`, verificação via JWKS) — a troca do `code` da\n * authorization code flow é responsabilidade do host/frontend, antes de chegar aqui.\n */\nexport const keycloakCallbackSchema = z.object({\n accessToken: z.string().min(1),\n})\n\nexport type KeycloakCallbackInput = z.infer<typeof keycloakCallbackSchema>\n"],"mappings":"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","names":["USER_ERROR_CODE","INVALID_CREDENTIALS","USER_NOT_FOUND","EMAIL_ALREADY_EXISTS","NOT_AUTHENTICATED","RESET_TOKEN_INVALID","RESET_TOKEN_EXPIRED","RESET_TOKEN_ALREADY_USED","WEAK_PASSWORD","PROVIDER_MISCONFIGURED","PROVIDER_DISABLED","CONFIG_MISSING","UserError","Error","statusCode","code","details","params","message","name","InvalidCredentialsError","UserNotFoundError","EmailAlreadyExistsError","NotAuthenticatedError","ResetTokenInvalidError","ResetTokenExpiredError","ResetTokenAlreadyUsedError","WeakPasswordError","ProviderMisconfiguredError","ProviderDisabledError","ConfigMissingError","field","USER_EVENT","USER_CREATED","USER_UPDATED","LOGIN_SUCCEEDED","LOGIN_FAILED","PASSWORD_CHANGED","PASSWORD_RESET_REQUESTED","PASSWORD_RESET_COMPLETED","PROFILE_UPDATED","AUTH_PROVIDER_TYPE","LOCAL","OAUTH2","OIDC","z","PASSWORD_MIN_LENGTH","PASSWORD_MAX_LENGTH","localCredentialsSchema","object","email","string","trim","toLowerCase","max","password","min","requestPasswordResetSchema","confirmPasswordResetSchema","token","newPassword","updateProfileSchema","name","optional","createUserSchema","role","keycloakCallbackSchema","accessToken"]}
1
+ {"version":3,"sources":["../src/avatar.ts","../src/errors.ts","../src/events.ts","../src/providers.ts","../src/schemas.ts"],"sourcesContent":["/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * A foto de perfil: porta de armazenamento e a validação que roda antes de qualquer rede.\n */\n\n/** 2 MB. Foto de perfil é exibida em 40px numa tabela; o que passa disso é desperdício de banda. */\nexport const AVATAR_MAX_BYTES = 2 * 1024 * 1024\n\n/**\n * Lista fechada, e não `image/*`: `image/svg+xml` é um documento com script dentro, e servido do\n * mesmo domínio viraria XSS. Nenhum destes três executa nada.\n */\nexport const AVATAR_CONTENT_TYPES = ['image/jpeg', 'image/png', 'image/webp'] as const\nexport type AvatarContentType = (typeof AVATAR_CONTENT_TYPES)[number]\n\nexport const AVATAR_REJECTION = {\n TOO_LARGE: 'avatar_too_large',\n UNSUPPORTED_TYPE: 'avatar_unsupported_type',\n EMPTY: 'avatar_empty',\n} as const\nexport type AvatarRejection = (typeof AVATAR_REJECTION)[keyof typeof AVATAR_REJECTION]\n\nexport type CheckAvatarParams = {\n readonly contentType: string\n readonly byteLength: number\n}\n\n/**\n * Devolve o motivo, e não um booleano: quem chama precisa dizer à pessoa se o arquivo é grande\n * demais ou se é do tipo errado — são duas correções diferentes.\n */\nexport function checkAvatar(params: CheckAvatarParams): AvatarRejection | undefined {\n if (params.byteLength <= 0) return AVATAR_REJECTION.EMPTY\n if (params.byteLength > AVATAR_MAX_BYTES) return AVATAR_REJECTION.TOO_LARGE\n if (!AVATAR_CONTENT_TYPES.includes(params.contentType as AvatarContentType)) {\n return AVATAR_REJECTION.UNSUPPORTED_TYPE\n }\n return undefined\n}\n\nexport type PutAvatarParams = {\n readonly userId: string\n readonly body: Uint8Array\n readonly contentType: AvatarContentType\n}\n\n/**\n * O host pluga o armazenamento; o módulo não sabe se é S3, disco ou memória.\n *\n * Sem esta porta o módulo não publica as rotas de foto — capacidade por ausência. Um produto sem\n * bucket não tem uma foto quebrada: não tem foto.\n */\nexport type AvatarStoragePort = {\n /** Grava e devolve a chave opaca a guardar na linha do usuário. */\n put(params: PutAvatarParams): Promise<string>\n /**\n * URL de leitura de vida curta.\n *\n * Assinada, e não pública: um rosto de funcionário não é o logo da empresa, e bucket aberto\n * indexa. Curta porque ela viaja na resposta da listagem, que passa por log e por cache.\n */\n sign(key: string): Promise<string>\n /** Remove a foto anterior; falhar aqui não pode derrubar a troca (o lixo é varrido depois). */\n remove?(key: string): Promise<void>\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nexport const USER_ERROR_CODE = {\n INVALID_CREDENTIALS: 'USER_INVALID_CREDENTIALS',\n USER_NOT_FOUND: 'USER_NOT_FOUND',\n EMAIL_ALREADY_EXISTS: 'USER_EMAIL_ALREADY_EXISTS',\n NOT_AUTHENTICATED: 'USER_NOT_AUTHENTICATED',\n RESET_TOKEN_INVALID: 'USER_RESET_TOKEN_INVALID',\n RESET_TOKEN_EXPIRED: 'USER_RESET_TOKEN_EXPIRED',\n RESET_TOKEN_ALREADY_USED: 'USER_RESET_TOKEN_ALREADY_USED',\n WEAK_PASSWORD: 'USER_WEAK_PASSWORD',\n PROVIDER_MISCONFIGURED: 'USER_PROVIDER_MISCONFIGURED',\n PROVIDER_DISABLED: 'USER_PROVIDER_DISABLED',\n CONFIG_MISSING: 'USER_CONFIG_MISSING',\n AVATAR_REJECTED: 'USER_AVATAR_REJECTED',\n} as const\n\nexport type UserErrorCode = (typeof USER_ERROR_CODE)[keyof typeof USER_ERROR_CODE]\n\nexport class UserError extends Error {\n readonly statusCode: number\n readonly code: UserErrorCode\n readonly details?: unknown\n\n constructor(params: { message: string; statusCode: number; code: UserErrorCode; details?: unknown }) {\n super(params.message)\n this.name = 'UserError'\n this.statusCode = params.statusCode\n this.code = params.code\n this.details = params.details\n }\n}\n\nexport class InvalidCredentialsError extends UserError {\n constructor() {\n super({\n message: 'Invalid credentials',\n statusCode: 401,\n code: USER_ERROR_CODE.INVALID_CREDENTIALS,\n })\n this.name = 'InvalidCredentialsError'\n }\n}\n\nexport class UserNotFoundError extends UserError {\n constructor() {\n super({\n message: 'User not found',\n statusCode: 404,\n code: USER_ERROR_CODE.USER_NOT_FOUND,\n })\n this.name = 'UserNotFoundError'\n }\n}\n\nexport class EmailAlreadyExistsError extends UserError {\n constructor() {\n super({\n message: 'Email already exists',\n statusCode: 409,\n code: USER_ERROR_CODE.EMAIL_ALREADY_EXISTS,\n })\n this.name = 'EmailAlreadyExistsError'\n }\n}\n\nexport class NotAuthenticatedError extends UserError {\n constructor() {\n super({\n message: 'Not authenticated',\n statusCode: 401,\n code: USER_ERROR_CODE.NOT_AUTHENTICATED,\n })\n this.name = 'NotAuthenticatedError'\n }\n}\n\nexport class ResetTokenInvalidError extends UserError {\n constructor() {\n super({\n message: 'Invalid reset token',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_INVALID,\n })\n this.name = 'ResetTokenInvalidError'\n }\n}\n\nexport class ResetTokenExpiredError extends UserError {\n constructor() {\n super({\n message: 'Reset token expired',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_EXPIRED,\n })\n this.name = 'ResetTokenExpiredError'\n }\n}\n\nexport class ResetTokenAlreadyUsedError extends UserError {\n constructor() {\n super({\n message: 'Reset token already used',\n statusCode: 400,\n code: USER_ERROR_CODE.RESET_TOKEN_ALREADY_USED,\n })\n this.name = 'ResetTokenAlreadyUsedError'\n }\n}\n\nexport class WeakPasswordError extends UserError {\n constructor() {\n super({\n message: 'Password does not meet security requirements',\n statusCode: 400,\n code: USER_ERROR_CODE.WEAK_PASSWORD,\n })\n this.name = 'WeakPasswordError'\n }\n}\n\nexport class ProviderMisconfiguredError extends UserError {\n constructor(details?: unknown) {\n super({\n message: 'Authentication provider is misconfigured',\n statusCode: 500,\n code: USER_ERROR_CODE.PROVIDER_MISCONFIGURED,\n details,\n })\n this.name = 'ProviderMisconfiguredError'\n }\n}\n\nexport class ProviderDisabledError extends UserError {\n constructor() {\n super({\n message: 'Authentication provider is not available',\n statusCode: 503,\n code: USER_ERROR_CODE.PROVIDER_DISABLED,\n })\n this.name = 'ProviderDisabledError'\n }\n}\n\nexport class ConfigMissingError extends UserError {\n constructor(field: string) {\n super({\n message: `Missing required configuration: ${field}`,\n statusCode: 500,\n code: USER_ERROR_CODE.CONFIG_MISSING,\n details: { field },\n })\n this.name = 'ConfigMissingError'\n }\n}\n\n/**\n * O `details.reason` e o motivo estavel (`AvatarRejection`), nao a frase.\n *\n * \"Grande demais\" e \"tipo nao suportado\" pedem correcoes diferentes, e quem monta a tela precisa\n * distinguir os dois sem casar string traduzida.\n */\nexport class AvatarRejectedError extends UserError {\n constructor(reason: string) {\n super({\n message: 'Avatar rejected',\n statusCode: 400,\n code: USER_ERROR_CODE.AVATAR_REJECTED,\n details: { reason },\n })\n this.name = 'AvatarRejectedError'\n }\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport type { UserProfile } from './user.types'\n\nexport const USER_EVENT = {\n USER_CREATED: 'user.user.created',\n USER_UPDATED: 'user.user.updated',\n LOGIN_SUCCEEDED: 'user.login.succeeded',\n LOGIN_FAILED: 'user.login.failed',\n PASSWORD_CHANGED: 'user.password.changed',\n PASSWORD_RESET_REQUESTED: 'user.password_reset.requested',\n PASSWORD_RESET_COMPLETED: 'user.password_reset.completed',\n PROFILE_UPDATED: 'user.profile.updated',\n} as const\n\nexport type UserEventType = (typeof USER_EVENT)[keyof typeof USER_EVENT]\n\nexport type BaseEvent = {\n readonly companyId?: string\n readonly occurredAt: Date\n}\n\nexport type UserCreatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.USER_CREATED\n readonly userId: string\n readonly email: string\n}\n\nexport type UserUpdatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.USER_UPDATED\n readonly userId: string\n readonly user: UserProfile\n}\n\nexport type LoginSucceededEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.LOGIN_SUCCEEDED\n readonly userId: string\n readonly email: string\n readonly ipAddress: string\n}\n\nexport type LoginFailedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.LOGIN_FAILED\n readonly email: string\n readonly ipAddress: string\n readonly reason: string\n}\n\nexport type PasswordChangedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_CHANGED\n readonly userId: string\n}\n\nexport type PasswordResetRequestedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_RESET_REQUESTED\n readonly email: string\n readonly resetUrl: string\n}\n\nexport type PasswordResetCompletedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PASSWORD_RESET_COMPLETED\n readonly userId: string\n readonly email: string\n}\n\nexport type ProfileUpdatedEvent = BaseEvent & {\n readonly type: typeof USER_EVENT.PROFILE_UPDATED\n readonly userId: string\n readonly user: UserProfile\n}\n\nexport type UserDomainEvent =\n | UserCreatedEvent\n | UserUpdatedEvent\n | LoginSucceededEvent\n | LoginFailedEvent\n | PasswordChangedEvent\n | PasswordResetRequestedEvent\n | PasswordResetCompletedEvent\n | ProfileUpdatedEvent\n\nexport type UserHooks = {\n readonly onUserCreated?: (event: UserCreatedEvent) => Promise<void> | void\n readonly onUserUpdated?: (event: UserUpdatedEvent) => Promise<void> | void\n readonly onLoginSucceeded?: (event: LoginSucceededEvent) => Promise<void> | void\n readonly onLoginFailed?: (event: LoginFailedEvent) => Promise<void> | void\n readonly onPasswordChanged?: (event: PasswordChangedEvent) => Promise<void> | void\n readonly onPasswordResetRequested?: (event: PasswordResetRequestedEvent) => Promise<void> | void\n readonly onPasswordResetCompleted?: (event: PasswordResetCompletedEvent) => Promise<void> | void\n readonly onProfileUpdated?: (event: ProfileUpdatedEvent) => Promise<void> | void\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport type { UserSession } from './user.types'\n\nexport const AUTH_PROVIDER_TYPE = {\n LOCAL: 'local',\n OAUTH2: 'oauth2',\n OIDC: 'oidc',\n} as const\n\nexport type AuthProviderType = (typeof AUTH_PROVIDER_TYPE)[keyof typeof AUTH_PROVIDER_TYPE]\n\nexport type AuthProviderInterface<TCredentials = unknown> = {\n readonly id: string\n readonly type: AuthProviderType\n authenticate(params: { readonly credentials: TCredentials; readonly ipAddress?: string }): Promise<UserSession>\n}\n\nexport type AttributeMappingRule<TClaims extends Record<string, unknown>> =\n | { readonly from: keyof TClaims }\n | { readonly value: string }\n\nexport type AttributeMapping<TClaims extends Record<string, unknown>> = {\n readonly email: AttributeMappingRule<TClaims>\n readonly name?: AttributeMappingRule<TClaims>\n readonly role?: AttributeMappingRule<TClaims>\n}\n\n/**\n * `issue` devolve o token cru; `rotate`/`revoke` recebem o **sha256 hex** dele — o token cru nunca\n * chega ao armazenamento, então um dump do Redis ou da tabela não vale sessão.\n *\n * `revokeAllForUser` é o que faz uma troca de senha realmente encerrar as sessões abertas. Sem ela,\n * a sessão comprometida sobrevive à redefinição feita justamente para matá-la — por isso é\n * obrigatória no contrato, e não uma capacidade opcional.\n */\nexport type RefreshTokenStorePort = {\n issue(params: { readonly userId: string; readonly expiresInSeconds: number }): Promise<string>\n rotate(params: {\n readonly tokenHash: string\n readonly newExpiresInSeconds: number\n }): Promise<{ readonly token: string; readonly userId: string } | null>\n revoke(params: { readonly tokenHash: string }): Promise<void>\n revokeAllForUser(params: { readonly userId: string }): Promise<void>\n}\n\n/**\n * Forma **idêntica** à de `@adatechnology/notification-contracts` — redeclarada, e não importada,\n * só para um pacote de contratos não arrastar outro domínio de runtime junto. Como o TypeScript é\n * estrutural, qualquer driver de `@adatechnology/email-provider` (`createSmtpEmailProvider`,\n * `createResendEmailProvider`, `createSesEmailProvider`) entra direto em `providers.email`, sem\n * adapter no host.\n *\n * Divergir daqui é o que quebra essa troca: a redeclaração só serve se as duas formas forem a\n * mesma, e nada no build de um pacote isolado avisa quando deixam de ser.\n */\nexport type SendEmailParams = {\n readonly to: string\n readonly subject: string\n readonly html: string\n readonly text: string\n readonly replyTo?: string\n readonly idempotencyKey?: string\n}\n\n/**\n * União discriminada, não `{ success: boolean }`: quem chama precisa separar endereço inválido\n * (suprimir, nunca reenviar) de falha temporária (reagendar com backoff) de falha definitiva.\n * Colapsar isso num booleano joga fora justamente a informação que decide a ação seguinte.\n */\nexport type DeliveryAttemptResult =\n | { readonly outcome: 'sent'; readonly providerMessageId?: string }\n | { readonly outcome: 'invalid_target'; readonly errorCode: string }\n | { readonly outcome: 'retriable'; readonly errorCode: string; readonly retryAfterSeconds?: number }\n | { readonly outcome: 'permanent'; readonly errorCode: string }\n\nexport type EmailDriverPort = {\n readonly driver: string\n send(params: SendEmailParams): Promise<DeliveryAttemptResult>\n}\n\nexport type ClockPort = {\n now(): Date\n}\n\nexport type LogMeta = Readonly<Record<string, unknown>>\n\n/** Mesma forma do `LoggerPort` dos outros contratos do ecossistema — o host escreve um adapter só. */\nexport type LoggerPort = {\n error(message: string, meta?: LogMeta): void\n warn(message: string, meta?: LogMeta): void\n info(message: string, meta?: LogMeta): void\n debug(message: string, meta?: LogMeta): void\n}\n\nexport type TenancyConfig = { readonly mode: 'single'; readonly defaultCompanyId: string } | { readonly mode: 'multi' }\n\n/**\n * `issuer`/`audience` são opcionais, mas quando declarados valem na assinatura **e** na\n * verificação: um token emitido para outra plateia é recusado. Um host que já emite JWT por conta\n * própria (migração gradual, dois emissores sobre o mesmo segredo) precisa declarar os mesmos\n * valores aqui, ou os dois lados não reconhecem o token um do outro.\n */\nexport type AccessTokenConfig = {\n readonly secret: string\n readonly expiresInSeconds?: number\n readonly issuer?: string\n readonly audience?: string\n}\n\nexport type RefreshTokenConfig = {\n readonly expiresInSeconds?: number\n}\n\nexport type PasswordResetEmailContent = {\n readonly subject: string\n readonly html: string\n readonly text: string\n}\n\nexport type PasswordResetEmailParams = {\n readonly resetUrl: string\n readonly name: string\n readonly expiresInSeconds: number\n}\n\nexport type PasswordResetConfig = {\n readonly resetUrlTemplate: string // must contain {token}\n readonly tokenExpiresInSeconds?: number\n /**\n * Texto do e-mail de redefinição. O módulo traz um padrão neutro, sem nome de produto e sem\n * marca — copy é vocabulário do host (`pluggable-module.md`), e cinco produtos consomem este\n * pacote. Quem quiser template versionado e pré-visualizável monta aqui em cima de\n * `renderTemplate` do `@adatechnology/notification-contracts`.\n */\n readonly buildEmail?: (params: PasswordResetEmailParams) => PasswordResetEmailContent\n}\n\nexport type KeycloakConfig = {\n readonly realm: string\n readonly authServerUrl: string\n readonly clientId: string\n readonly clientSecret?: string\n readonly attributeMapping?: AttributeMapping<Record<string, unknown>>\n}\n\nexport type UserModuleConfig = {\n readonly tenancy: TenancyConfig\n readonly accessToken: AccessTokenConfig\n readonly refreshToken?: RefreshTokenConfig\n readonly passwordReset?: PasswordResetConfig\n readonly keycloak?: KeycloakConfig\n}\n\n/**\n * `keycloak` não entra aqui: a verificação de token é plumbing específico do módulo (ver\n * `KeycloakVerifierPort` em `user-module`), não um contrato genérico reutilizável por outro host.\n */\nexport type UserModuleProviders = {\n readonly refreshTokenStore?: RefreshTokenStorePort\n readonly email?: EmailDriverPort\n readonly clock?: ClockPort\n readonly logger?: LoggerPort\n}\n","/**\n * Copyright (c) 2026 Ada Technology. All rights reserved.\n *\n * This source code is proprietary and confidential. Unauthorized copying,\n * modification, distribution, or use of this file, via any medium, is\n * strictly prohibited without prior written permission from Ada Technology.\n */\n\nimport { z } from 'zod'\n\nconst PASSWORD_MIN_LENGTH = 8\nconst PASSWORD_MAX_LENGTH = 128\n\nexport const localCredentialsSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n password: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n})\n\nexport type LocalCredentials = z.infer<typeof localCredentialsSchema>\n\nexport const requestPasswordResetSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n})\n\nexport type RequestPasswordResetInput = z.infer<typeof requestPasswordResetSchema>\n\nexport const confirmPasswordResetSchema = z.object({\n token: z.string().min(1),\n newPassword: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n})\n\nexport type ConfirmPasswordResetInput = z.infer<typeof confirmPasswordResetSchema>\n\nexport const updateProfileSchema = z.object({\n name: z.string().trim().min(1).max(255).optional(),\n})\n\nexport type UpdateProfileInput = z.infer<typeof updateProfileSchema>\n\nexport const createUserSchema = z.object({\n email: z.string().trim().toLowerCase().email().max(320),\n name: z.string().trim().min(1).max(255),\n password: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),\n role: z.string().min(1).max(40),\n})\n\nexport type CreateUserInput = z.infer<typeof createUserSchema>\n\n/**\n * `accessToken`, não `code`/`state`: o módulo verifica localmente um token já emitido pelo\n * Keycloak (`@adatechnology/auth-keycloak`, verificação via JWKS) — a troca do `code` da\n * authorization code flow é responsabilidade do host/frontend, antes de chegar aqui.\n */\nexport const keycloakCallbackSchema = z.object({\n accessToken: z.string().min(1),\n})\n\nexport type KeycloakCallbackInput = z.infer<typeof keycloakCallbackSchema>\n"],"mappings":"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","names":["AVATAR_MAX_BYTES","AVATAR_CONTENT_TYPES","AVATAR_REJECTION","TOO_LARGE","UNSUPPORTED_TYPE","EMPTY","checkAvatar","params","byteLength","includes","contentType","undefined","USER_ERROR_CODE","INVALID_CREDENTIALS","USER_NOT_FOUND","EMAIL_ALREADY_EXISTS","NOT_AUTHENTICATED","RESET_TOKEN_INVALID","RESET_TOKEN_EXPIRED","RESET_TOKEN_ALREADY_USED","WEAK_PASSWORD","PROVIDER_MISCONFIGURED","PROVIDER_DISABLED","CONFIG_MISSING","AVATAR_REJECTED","UserError","Error","statusCode","code","details","params","message","name","InvalidCredentialsError","UserNotFoundError","EmailAlreadyExistsError","NotAuthenticatedError","ResetTokenInvalidError","ResetTokenExpiredError","ResetTokenAlreadyUsedError","WeakPasswordError","ProviderMisconfiguredError","ProviderDisabledError","ConfigMissingError","field","AvatarRejectedError","reason","USER_EVENT","USER_CREATED","USER_UPDATED","LOGIN_SUCCEEDED","LOGIN_FAILED","PASSWORD_CHANGED","PASSWORD_RESET_REQUESTED","PASSWORD_RESET_COMPLETED","PROFILE_UPDATED","AUTH_PROVIDER_TYPE","LOCAL","OAUTH2","OIDC","z","PASSWORD_MIN_LENGTH","PASSWORD_MAX_LENGTH","localCredentialsSchema","object","email","string","trim","toLowerCase","max","password","min","requestPasswordResetSchema","confirmPasswordResetSchema","token","newPassword","updateProfileSchema","name","optional","createUserSchema","role","keycloakCallbackSchema","accessToken"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adatechnology/user-contracts",
3
- "version": "0.1.0-rc.1",
3
+ "version": "0.1.0-rc.3",
4
4
  "license": "MIT",
5
5
  "description": "Shared types, zod schemas, error codes and port interfaces for user authentication (contracts only — no runtime behavior)",
6
6
  "publishConfig": {
@@ -25,7 +25,8 @@
25
25
  "devDependencies": {
26
26
  "@types/bun": "1.3.14",
27
27
  "tsup": "^8.5.1",
28
- "typescript": "^5.7.3"
28
+ "typescript": "^5.7.3",
29
+ "@adatechnology/notification-contracts": "0.1.0-rc.6"
29
30
  },
30
31
  "scripts": {
31
32
  "build": "tsup",