@adatechnology/user-contracts 0.1.0 → 0.2.0

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
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  NotAuthenticatedError: () => NotAuthenticatedError,
33
33
  ProviderDisabledError: () => ProviderDisabledError,
34
34
  ProviderMisconfiguredError: () => ProviderMisconfiguredError,
35
+ REFRESH_COOKIE_SAME_SITE: () => REFRESH_COOKIE_SAME_SITE,
35
36
  ResetTokenAlreadyUsedError: () => ResetTokenAlreadyUsedError,
36
37
  ResetTokenExpiredError: () => ResetTokenExpiredError,
37
38
  ResetTokenInvalidError: () => ResetTokenInvalidError,
@@ -284,6 +285,12 @@ var AUTH_PROVIDER_TYPE = {
284
285
  OAUTH2: "oauth2",
285
286
  OIDC: "oidc"
286
287
  };
288
+ var REFRESH_COOKIE_SAME_SITE = {
289
+ /** Padrão. A api e a tela compartilham o site registrável. */
290
+ LAX: "lax",
291
+ /** A tela vive em outro site. Exige HTTPS — o cookie já sai `Secure` sempre. */
292
+ NONE: "none"
293
+ };
287
294
 
288
295
  // src/schemas.ts
289
296
  var import_zod = require("zod");
@@ -325,6 +332,7 @@ var keycloakCallbackSchema = import_zod.z.object({
325
332
  NotAuthenticatedError,
326
333
  ProviderDisabledError,
327
334
  ProviderMisconfiguredError,
335
+ REFRESH_COOKIE_SAME_SITE,
328
336
  ResetTokenAlreadyUsedError,
329
337
  ResetTokenExpiredError,
330
338
  ResetTokenInvalidError,
@@ -1 +1 @@
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"]}
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\n/**\n * `Lax` não é enviado em requisição cross-site — e `fetch` nunca conta como navegação de topo.\n *\n * Cross-site aqui é decidido pelo site registrável (eTLD+1), não pelo domínio pai: dois serviços em\n * `*.up.railway.app` são cross-site entre si, porque `railway.app` está na Public Suffix List. Web e\n * api em subdomínios de um domínio próprio (`app.` e `api.` de `exemplo.com.br`) são same-site, e aí\n * `lax` é o certo.\n */\nexport const REFRESH_COOKIE_SAME_SITE = {\n /** Padrão. A api e a tela compartilham o site registrável. */\n LAX: 'lax',\n /** A tela vive em outro site. Exige HTTPS — o cookie já sai `Secure` sempre. */\n NONE: 'none',\n} as const\n\nexport type RefreshCookieSameSite = (typeof REFRESH_COOKIE_SAME_SITE)[keyof typeof REFRESH_COOKIE_SAME_SITE]\n\nexport type RefreshTokenConfig = {\n readonly expiresInSeconds?: number\n /**\n * Ausente = `lax`, que é o comportamento de sempre.\n *\n * `none` só quando a tela estiver em outro site registrável: ele permite que qualquer origem\n * inicie requisição com o cookie anexado, e a defesa contra CSRF passa a ser inteiramente do CORS\n * e da checagem de origem do host.\n */\n readonly sameSite?: RefreshCookieSameSite\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","REFRESH_COOKIE_SAME_SITE","LAX","NONE","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
@@ -379,8 +379,31 @@ type AccessTokenConfig = {
379
379
  readonly issuer?: string;
380
380
  readonly audience?: string;
381
381
  };
382
+ /**
383
+ * `Lax` não é enviado em requisição cross-site — e `fetch` nunca conta como navegação de topo.
384
+ *
385
+ * Cross-site aqui é decidido pelo site registrável (eTLD+1), não pelo domínio pai: dois serviços em
386
+ * `*.up.railway.app` são cross-site entre si, porque `railway.app` está na Public Suffix List. Web e
387
+ * api em subdomínios de um domínio próprio (`app.` e `api.` de `exemplo.com.br`) são same-site, e aí
388
+ * `lax` é o certo.
389
+ */
390
+ declare const REFRESH_COOKIE_SAME_SITE: {
391
+ /** Padrão. A api e a tela compartilham o site registrável. */
392
+ readonly LAX: "lax";
393
+ /** A tela vive em outro site. Exige HTTPS — o cookie já sai `Secure` sempre. */
394
+ readonly NONE: "none";
395
+ };
396
+ type RefreshCookieSameSite = (typeof REFRESH_COOKIE_SAME_SITE)[keyof typeof REFRESH_COOKIE_SAME_SITE];
382
397
  type RefreshTokenConfig = {
383
398
  readonly expiresInSeconds?: number;
399
+ /**
400
+ * Ausente = `lax`, que é o comportamento de sempre.
401
+ *
402
+ * `none` só quando a tela estiver em outro site registrável: ele permite que qualquer origem
403
+ * inicie requisição com o cookie anexado, e a defesa contra CSRF passa a ser inteiramente do CORS
404
+ * e da checagem de origem do host.
405
+ */
406
+ readonly sameSite?: RefreshCookieSameSite;
384
407
  };
385
408
  type PasswordResetEmailContent = {
386
409
  readonly subject: string;
@@ -505,4 +528,4 @@ declare const keycloakCallbackSchema: z.ZodObject<{
505
528
  }>;
506
529
  type KeycloakCallbackInput = z.infer<typeof keycloakCallbackSchema>;
507
530
 
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 };
531
+ 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, REFRESH_COOKIE_SAME_SITE, type RefreshCookieSameSite, 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
@@ -379,8 +379,31 @@ type AccessTokenConfig = {
379
379
  readonly issuer?: string;
380
380
  readonly audience?: string;
381
381
  };
382
+ /**
383
+ * `Lax` não é enviado em requisição cross-site — e `fetch` nunca conta como navegação de topo.
384
+ *
385
+ * Cross-site aqui é decidido pelo site registrável (eTLD+1), não pelo domínio pai: dois serviços em
386
+ * `*.up.railway.app` são cross-site entre si, porque `railway.app` está na Public Suffix List. Web e
387
+ * api em subdomínios de um domínio próprio (`app.` e `api.` de `exemplo.com.br`) são same-site, e aí
388
+ * `lax` é o certo.
389
+ */
390
+ declare const REFRESH_COOKIE_SAME_SITE: {
391
+ /** Padrão. A api e a tela compartilham o site registrável. */
392
+ readonly LAX: "lax";
393
+ /** A tela vive em outro site. Exige HTTPS — o cookie já sai `Secure` sempre. */
394
+ readonly NONE: "none";
395
+ };
396
+ type RefreshCookieSameSite = (typeof REFRESH_COOKIE_SAME_SITE)[keyof typeof REFRESH_COOKIE_SAME_SITE];
382
397
  type RefreshTokenConfig = {
383
398
  readonly expiresInSeconds?: number;
399
+ /**
400
+ * Ausente = `lax`, que é o comportamento de sempre.
401
+ *
402
+ * `none` só quando a tela estiver em outro site registrável: ele permite que qualquer origem
403
+ * inicie requisição com o cookie anexado, e a defesa contra CSRF passa a ser inteiramente do CORS
404
+ * e da checagem de origem do host.
405
+ */
406
+ readonly sameSite?: RefreshCookieSameSite;
384
407
  };
385
408
  type PasswordResetEmailContent = {
386
409
  readonly subject: string;
@@ -505,4 +528,4 @@ declare const keycloakCallbackSchema: z.ZodObject<{
505
528
  }>;
506
529
  type KeycloakCallbackInput = z.infer<typeof keycloakCallbackSchema>;
507
530
 
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 };
531
+ 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, REFRESH_COOKIE_SAME_SITE, type RefreshCookieSameSite, 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
@@ -235,6 +235,12 @@ var AUTH_PROVIDER_TYPE = {
235
235
  OAUTH2: "oauth2",
236
236
  OIDC: "oidc"
237
237
  };
238
+ var REFRESH_COOKIE_SAME_SITE = {
239
+ /** Padrão. A api e a tela compartilham o site registrável. */
240
+ LAX: "lax",
241
+ /** A tela vive em outro site. Exige HTTPS — o cookie já sai `Secure` sempre. */
242
+ NONE: "none"
243
+ };
238
244
 
239
245
  // src/schemas.ts
240
246
  import { z } from "zod";
@@ -275,6 +281,7 @@ export {
275
281
  NotAuthenticatedError,
276
282
  ProviderDisabledError,
277
283
  ProviderMisconfiguredError,
284
+ REFRESH_COOKIE_SAME_SITE,
278
285
  ResetTokenAlreadyUsedError,
279
286
  ResetTokenExpiredError,
280
287
  ResetTokenInvalidError,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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"]}
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\n/**\n * `Lax` não é enviado em requisição cross-site — e `fetch` nunca conta como navegação de topo.\n *\n * Cross-site aqui é decidido pelo site registrável (eTLD+1), não pelo domínio pai: dois serviços em\n * `*.up.railway.app` são cross-site entre si, porque `railway.app` está na Public Suffix List. Web e\n * api em subdomínios de um domínio próprio (`app.` e `api.` de `exemplo.com.br`) são same-site, e aí\n * `lax` é o certo.\n */\nexport const REFRESH_COOKIE_SAME_SITE = {\n /** Padrão. A api e a tela compartilham o site registrável. */\n LAX: 'lax',\n /** A tela vive em outro site. Exige HTTPS — o cookie já sai `Secure` sempre. */\n NONE: 'none',\n} as const\n\nexport type RefreshCookieSameSite = (typeof REFRESH_COOKIE_SAME_SITE)[keyof typeof REFRESH_COOKIE_SAME_SITE]\n\nexport type RefreshTokenConfig = {\n readonly expiresInSeconds?: number\n /**\n * Ausente = `lax`, que é o comportamento de sempre.\n *\n * `none` só quando a tela estiver em outro site registrável: ele permite que qualquer origem\n * inicie requisição com o cookie anexado, e a defesa contra CSRF passa a ser inteiramente do CORS\n * e da checagem de origem do host.\n */\n readonly sameSite?: RefreshCookieSameSite\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","REFRESH_COOKIE_SAME_SITE","LAX","NONE","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",
3
+ "version": "0.2.0",
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": {