@hed-hog/core 0.0.390 → 0.0.393
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/README.md +39 -0
- package/dist/access-log/access-log.controller.d.ts.map +1 -1
- package/dist/access-log/access-log.controller.js +1 -1
- package/dist/access-log/access-log.controller.js.map +1 -1
- package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
- package/dist/access-log/access-log.interceptor.js +8 -5
- package/dist/access-log/access-log.interceptor.js.map +1 -1
- package/dist/access-log/access-log.service.d.ts +4 -0
- package/dist/access-log/access-log.service.d.ts.map +1 -1
- package/dist/access-log/access-log.service.js +31 -4
- package/dist/access-log/access-log.service.js.map +1 -1
- package/dist/access-log/dto/query-access-log.dto.d.ts +2 -0
- package/dist/access-log/dto/query-access-log.dto.d.ts.map +1 -1
- package/dist/access-log/dto/query-access-log.dto.js +5 -0
- package/dist/access-log/dto/query-access-log.dto.js.map +1 -1
- package/dist/ai/ai.controller.d.ts +12 -8
- package/dist/ai/ai.controller.d.ts.map +1 -1
- package/dist/ai/ai.controller.js +17 -2
- package/dist/ai/ai.controller.js.map +1 -1
- package/dist/ai/ai.module.d.ts.map +1 -1
- package/dist/ai/ai.module.js +3 -2
- package/dist/ai/ai.module.js.map +1 -1
- package/dist/ai/ai.service.d.ts +30 -1
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +97 -3
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/ai/dto/richtext-edit-html.dto.d.ts +5 -0
- package/dist/ai/dto/richtext-edit-html.dto.d.ts.map +1 -0
- package/dist/ai/dto/richtext-edit-html.dto.js +27 -0
- package/dist/ai/dto/richtext-edit-html.dto.js.map +1 -0
- package/dist/ai/richtext-ai.prompts.d.ts +5 -0
- package/dist/ai/richtext-ai.prompts.d.ts.map +1 -0
- package/dist/ai/richtext-ai.prompts.js +57 -0
- package/dist/ai/richtext-ai.prompts.js.map +1 -0
- package/dist/ai/richtext-ai.service.d.ts +34 -0
- package/dist/ai/richtext-ai.service.d.ts.map +1 -0
- package/dist/ai/richtext-ai.service.js +95 -0
- package/dist/ai/richtext-ai.service.js.map +1 -0
- package/dist/ai-instruction/ai-instruction.controller.d.ts +5 -5
- package/dist/auth/auth.controller.d.ts +66 -7
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +67 -23
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +2 -0
- package/dist/auth/auth.module.js.map +1 -1
- package/dist/auth/auth.service.d.ts +136 -4
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +246 -48
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/auth/dto/confirm-email.dto.d.ts +4 -0
- package/dist/auth/dto/confirm-email.dto.d.ts.map +1 -0
- package/dist/auth/dto/confirm-email.dto.js +22 -0
- package/dist/auth/dto/confirm-email.dto.js.map +1 -0
- package/dist/auth/dto/resend-confirmation.dto.d.ts +8 -0
- package/dist/auth/dto/resend-confirmation.dto.d.ts.map +1 -0
- package/dist/auth/dto/resend-confirmation.dto.js +36 -0
- package/dist/auth/dto/resend-confirmation.dto.js.map +1 -0
- package/dist/authorize/authorize.controller.d.ts +1 -1
- package/dist/authorize/authorize.controller.d.ts.map +1 -1
- package/dist/authorize/authorize.controller.js +4 -2
- package/dist/authorize/authorize.controller.js.map +1 -1
- package/dist/authorize/authorize.service.d.ts +7 -0
- package/dist/authorize/authorize.service.d.ts.map +1 -1
- package/dist/authorize/authorize.service.js +1 -1
- package/dist/authorize/authorize.service.js.map +1 -1
- package/dist/challenge/challenge.service.d.ts +18 -5
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +50 -3
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +17 -0
- package/dist/core.module.js.map +1 -1
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +6 -6
- package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -6
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +4 -4
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +4 -4
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +2 -2
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +2 -2
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -2
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -2
- package/dist/file/file.controller.d.ts +8 -1
- package/dist/file/file.controller.d.ts.map +1 -1
- package/dist/file/file.controller.js +45 -7
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.service.d.ts +128 -1
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +319 -8
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/image-optimization.service.d.ts +43 -0
- package/dist/file/image-optimization.service.d.ts.map +1 -1
- package/dist/file/image-optimization.service.js +154 -0
- package/dist/file/image-optimization.service.js.map +1 -1
- package/dist/file/provider/abstract.provider.d.ts +16 -0
- package/dist/file/provider/abstract.provider.d.ts.map +1 -1
- package/dist/file/provider/abstract.provider.js +4 -0
- package/dist/file/provider/abstract.provider.js.map +1 -1
- package/dist/file/provider/gcs.provider.d.ts +2 -0
- package/dist/file/provider/gcs.provider.d.ts.map +1 -1
- package/dist/file/provider/gcs.provider.js +10 -0
- package/dist/file/provider/gcs.provider.js.map +1 -1
- package/dist/file/provider/local.provider.d.ts +1 -0
- package/dist/file/provider/local.provider.d.ts.map +1 -1
- package/dist/file/provider/local.provider.js +13 -0
- package/dist/file/provider/local.provider.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts +5 -0
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +15 -0
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/file/provider/spaces.provider.d.ts +5 -0
- package/dist/file/provider/spaces.provider.d.ts.map +1 -1
- package/dist/file/provider/spaces.provider.js +16 -0
- package/dist/file/provider/spaces.provider.js.map +1 -1
- package/dist/impersonation/dto/redeem-impersonation.dto.d.ts +4 -0
- package/dist/impersonation/dto/redeem-impersonation.dto.d.ts.map +1 -0
- package/dist/impersonation/dto/redeem-impersonation.dto.js +23 -0
- package/dist/impersonation/dto/redeem-impersonation.dto.js.map +1 -0
- package/dist/impersonation/dto/start-impersonation.dto.d.ts +7 -0
- package/dist/impersonation/dto/start-impersonation.dto.d.ts.map +1 -0
- package/dist/impersonation/dto/start-impersonation.dto.js +27 -0
- package/dist/impersonation/dto/start-impersonation.dto.js.map +1 -0
- package/dist/impersonation/impersonation-restrictions.d.ts +26 -0
- package/dist/impersonation/impersonation-restrictions.d.ts.map +1 -0
- package/dist/impersonation/impersonation-restrictions.js +76 -0
- package/dist/impersonation/impersonation-restrictions.js.map +1 -0
- package/dist/impersonation/impersonation.controller.d.ts +90 -0
- package/dist/impersonation/impersonation.controller.d.ts.map +1 -0
- package/dist/impersonation/impersonation.controller.js +135 -0
- package/dist/impersonation/impersonation.controller.js.map +1 -0
- package/dist/impersonation/impersonation.module.d.ts +8 -0
- package/dist/impersonation/impersonation.module.d.ts.map +1 -0
- package/dist/impersonation/impersonation.module.js +44 -0
- package/dist/impersonation/impersonation.module.js.map +1 -0
- package/dist/impersonation/impersonation.service.d.ts +164 -0
- package/dist/impersonation/impersonation.service.d.ts.map +1 -0
- package/dist/impersonation/impersonation.service.js +408 -0
- package/dist/impersonation/impersonation.service.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/integration/services/outbox-polling.coordinator.d.ts +2 -0
- package/dist/integration/services/outbox-polling.coordinator.d.ts.map +1 -1
- package/dist/integration/services/outbox-polling.coordinator.js +50 -30
- package/dist/integration/services/outbox-polling.coordinator.js.map +1 -1
- package/dist/integration/services/outbox.service.d.ts.map +1 -1
- package/dist/integration/services/outbox.service.js +5 -1
- package/dist/integration/services/outbox.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.js +3 -0
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.d.ts +14 -0
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +26 -0
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/integration-profile/testers/email.testers.d.ts.map +1 -1
- package/dist/integration-profile/testers/email.testers.js +28 -0
- package/dist/integration-profile/testers/email.testers.js.map +1 -1
- package/dist/language/en.json +11 -0
- package/dist/language/pt.json +11 -0
- package/dist/mail/dto/send.dto.d.ts +38 -1
- package/dist/mail/dto/send.dto.d.ts.map +1 -1
- package/dist/mail/dto/send.dto.js +10 -0
- package/dist/mail/dto/send.dto.js.map +1 -1
- package/dist/mail/mail.controller.d.ts +1 -0
- package/dist/mail/mail.controller.d.ts.map +1 -1
- package/dist/mail/mail.module.d.ts.map +1 -1
- package/dist/mail/mail.module.js +2 -0
- package/dist/mail/mail.module.js.map +1 -1
- package/dist/mail/mail.service.d.ts +48 -4
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +187 -35
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mail-delivery/mail-delivery-provision.service.d.ts +21 -0
- package/dist/mail-delivery/mail-delivery-provision.service.d.ts.map +1 -0
- package/dist/mail-delivery/mail-delivery-provision.service.js +64 -0
- package/dist/mail-delivery/mail-delivery-provision.service.js.map +1 -0
- package/dist/mail-delivery/mail-delivery.constants.d.ts +17 -0
- package/dist/mail-delivery/mail-delivery.constants.d.ts.map +1 -0
- package/dist/mail-delivery/mail-delivery.constants.js +31 -0
- package/dist/mail-delivery/mail-delivery.constants.js.map +1 -0
- package/dist/mail-delivery/mail-delivery.controller.d.ts +19 -0
- package/dist/mail-delivery/mail-delivery.controller.d.ts.map +1 -0
- package/dist/mail-delivery/mail-delivery.controller.js +53 -0
- package/dist/mail-delivery/mail-delivery.controller.js.map +1 -0
- package/dist/mail-delivery/mail-delivery.module.d.ts +7 -0
- package/dist/mail-delivery/mail-delivery.module.d.ts.map +1 -0
- package/dist/mail-delivery/mail-delivery.module.js +36 -0
- package/dist/mail-delivery/mail-delivery.module.js.map +1 -0
- package/dist/mail-delivery/mail-delivery.service.d.ts +38 -0
- package/dist/mail-delivery/mail-delivery.service.d.ts.map +1 -0
- package/dist/mail-delivery/mail-delivery.service.js +140 -0
- package/dist/mail-delivery/mail-delivery.service.js.map +1 -0
- package/dist/mail-delivery/ses-event.parser.d.ts +27 -0
- package/dist/mail-delivery/ses-event.parser.d.ts.map +1 -0
- package/dist/mail-delivery/ses-event.parser.js +131 -0
- package/dist/mail-delivery/ses-event.parser.js.map +1 -0
- package/dist/mail-delivery/ses-webhook.service.d.ts +58 -0
- package/dist/mail-delivery/ses-webhook.service.d.ts.map +1 -0
- package/dist/mail-delivery/ses-webhook.service.js +214 -0
- package/dist/mail-delivery/ses-webhook.service.js.map +1 -0
- package/dist/mail-delivery/sns-message.util.d.ts +38 -0
- package/dist/mail-delivery/sns-message.util.d.ts.map +1 -0
- package/dist/mail-delivery/sns-message.util.js +109 -0
- package/dist/mail-delivery/sns-message.util.js.map +1 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts +5 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts.map +1 -1
- package/dist/mail-sent/dto/query-mail-sent-list.dto.js +6 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.js.map +1 -1
- package/dist/mail-sent/mail-sent.controller.d.ts +39 -0
- package/dist/mail-sent/mail-sent.controller.d.ts.map +1 -1
- package/dist/mail-sent/mail-sent.service.d.ts +39 -0
- package/dist/mail-sent/mail-sent.service.d.ts.map +1 -1
- package/dist/mail-sent/mail-sent.service.js +45 -4
- package/dist/mail-sent/mail-sent.service.js.map +1 -1
- package/dist/mailbox/mailbox-fetcher.registry.d.ts +100 -0
- package/dist/mailbox/mailbox-fetcher.registry.d.ts.map +1 -0
- package/dist/mailbox/mailbox-fetcher.registry.js +59 -0
- package/dist/mailbox/mailbox-fetcher.registry.js.map +1 -0
- package/dist/mailbox/mailbox.module.d.ts +8 -0
- package/dist/mailbox/mailbox.module.d.ts.map +1 -0
- package/dist/mailbox/mailbox.module.js +27 -0
- package/dist/mailbox/mailbox.module.js.map +1 -0
- package/dist/meeting/meeting-provider.registry.d.ts +155 -0
- package/dist/meeting/meeting-provider.registry.d.ts.map +1 -0
- package/dist/meeting/meeting-provider.registry.js +53 -0
- package/dist/meeting/meeting-provider.registry.js.map +1 -0
- package/dist/meeting/meeting.constants.d.ts +47 -0
- package/dist/meeting/meeting.constants.d.ts.map +1 -0
- package/dist/meeting/meeting.constants.js +21 -0
- package/dist/meeting/meeting.constants.js.map +1 -0
- package/dist/meeting/meeting.module.d.ts +7 -0
- package/dist/meeting/meeting.module.d.ts.map +1 -0
- package/dist/meeting/meeting.module.js +26 -0
- package/dist/meeting/meeting.module.js.map +1 -0
- package/dist/middlewares/locale-injection.middleware.d.ts +12 -0
- package/dist/middlewares/locale-injection.middleware.d.ts.map +1 -1
- package/dist/middlewares/locale-injection.middleware.js +20 -4
- package/dist/middlewares/locale-injection.middleware.js.map +1 -1
- package/dist/oauth/providers/google.provider.d.ts +7 -0
- package/dist/oauth/providers/google.provider.d.ts.map +1 -1
- package/dist/oauth/providers/google.provider.js +43 -13
- package/dist/oauth/providers/google.provider.js.map +1 -1
- package/dist/observability/monitored-cron.d.ts +34 -0
- package/dist/observability/monitored-cron.d.ts.map +1 -0
- package/dist/observability/monitored-cron.js +118 -0
- package/dist/observability/monitored-cron.js.map +1 -0
- package/dist/pairing/dto/approve-reverse-pairing.dto.d.ts +12 -0
- package/dist/pairing/dto/approve-reverse-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/approve-reverse-pairing.dto.js +30 -0
- package/dist/pairing/dto/approve-reverse-pairing.dto.js.map +1 -0
- package/dist/pairing/dto/claim-pairing.dto.d.ts +11 -0
- package/dist/pairing/dto/claim-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/claim-pairing.dto.js +35 -0
- package/dist/pairing/dto/claim-pairing.dto.js.map +1 -0
- package/dist/pairing/dto/confirm-pairing.dto.d.ts +10 -0
- package/dist/pairing/dto/confirm-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/confirm-pairing.dto.js +24 -0
- package/dist/pairing/dto/confirm-pairing.dto.js.map +1 -0
- package/dist/pairing/dto/poll-reverse-pairing.dto.d.ts +5 -0
- package/dist/pairing/dto/poll-reverse-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/poll-reverse-pairing.dto.js +24 -0
- package/dist/pairing/dto/poll-reverse-pairing.dto.js.map +1 -0
- package/dist/pairing/dto/start-pairing.dto.d.ts +11 -0
- package/dist/pairing/dto/start-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/start-pairing.dto.js +30 -0
- package/dist/pairing/dto/start-pairing.dto.js.map +1 -0
- package/dist/pairing/dto/start-reverse-pairing.dto.d.ts +24 -0
- package/dist/pairing/dto/start-reverse-pairing.dto.d.ts.map +1 -0
- package/dist/pairing/dto/start-reverse-pairing.dto.js +47 -0
- package/dist/pairing/dto/start-reverse-pairing.dto.js.map +1 -0
- package/dist/pairing/pairing.controller.d.ts +118 -0
- package/dist/pairing/pairing.controller.d.ts.map +1 -0
- package/dist/pairing/pairing.controller.js +296 -0
- package/dist/pairing/pairing.controller.js.map +1 -0
- package/dist/pairing/pairing.module.d.ts +10 -0
- package/dist/pairing/pairing.module.d.ts.map +1 -0
- package/dist/pairing/pairing.module.js +40 -0
- package/dist/pairing/pairing.module.js.map +1 -0
- package/dist/pairing/pairing.service.d.ts +238 -0
- package/dist/pairing/pairing.service.d.ts.map +1 -0
- package/dist/pairing/pairing.service.js +495 -0
- package/dist/pairing/pairing.service.js.map +1 -0
- package/dist/pdf/pdf.service.d.ts +3 -6
- package/dist/pdf/pdf.service.d.ts.map +1 -1
- package/dist/pdf/pdf.service.js +8 -20
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/profile/profile.controller.d.ts +11 -0
- package/dist/profile/profile.controller.d.ts.map +1 -1
- package/dist/profile/profile.service.d.ts +11 -0
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +20 -11
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/redis/redis-pubsub.d.ts.map +1 -1
- package/dist/redis/redis-pubsub.js +44 -0
- package/dist/redis/redis-pubsub.js.map +1 -1
- package/dist/role/guards/role.guard.d.ts +5 -0
- package/dist/role/guards/role.guard.d.ts.map +1 -1
- package/dist/role/guards/role.guard.js +41 -39
- package/dist/role/guards/role.guard.js.map +1 -1
- package/dist/session/session.controller.d.ts +2 -0
- package/dist/session/session.controller.d.ts.map +1 -1
- package/dist/session/session.service.d.ts +39 -0
- package/dist/session/session.service.d.ts.map +1 -1
- package/dist/session/session.service.js +91 -8
- package/dist/session/session.service.js.map +1 -1
- package/dist/setting/brand-asset.service.d.ts +48 -0
- package/dist/setting/brand-asset.service.d.ts.map +1 -0
- package/dist/setting/brand-asset.service.js +130 -0
- package/dist/setting/brand-asset.service.js.map +1 -0
- package/dist/setting/setting.module.d.ts.map +1 -1
- package/dist/setting/setting.module.js +3 -2
- package/dist/setting/setting.module.js.map +1 -1
- package/dist/setting/setting.service.d.ts +33 -0
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +88 -0
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/token/token.service.d.ts +23 -2
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +39 -4
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +8 -22
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.service.d.ts +44 -24
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +89 -6
- package/dist/user/user.service.js.map +1 -1
- package/dist/user-device/user-device.controller.d.ts +54 -0
- package/dist/user-device/user-device.controller.d.ts.map +1 -0
- package/dist/user-device/user-device.controller.js +103 -0
- package/dist/user-device/user-device.controller.js.map +1 -0
- package/dist/user-device/user-device.module.d.ts +3 -0
- package/dist/user-device/user-device.module.d.ts.map +1 -0
- package/dist/user-device/user-device.module.js +26 -0
- package/dist/user-device/user-device.module.js.map +1 -0
- package/dist/user-device/user-device.service.d.ts +71 -0
- package/dist/user-device/user-device.service.d.ts.map +1 -0
- package/dist/user-device/user-device.service.js +158 -0
- package/dist/user-device/user-device.service.js.map +1 -0
- package/dist/utils/device-key.d.ts +23 -0
- package/dist/utils/device-key.d.ts.map +1 -0
- package/dist/utils/device-key.js +31 -0
- package/dist/utils/device-key.js.map +1 -0
- package/dist/utils/geo-ip.d.ts +38 -0
- package/dist/utils/geo-ip.d.ts.map +1 -0
- package/dist/utils/geo-ip.js +122 -0
- package/dist/utils/geo-ip.js.map +1 -0
- package/dist/utils/locale-context.d.ts +10 -7
- package/dist/utils/locale-context.d.ts.map +1 -1
- package/dist/utils/locale-context.js +14 -8
- package/dist/utils/locale-context.js.map +1 -1
- package/dist/utils/security-event-vars.d.ts +56 -0
- package/dist/utils/security-event-vars.d.ts.map +1 -0
- package/dist/utils/security-event-vars.js +109 -0
- package/dist/utils/security-event-vars.js.map +1 -0
- package/dist/utils/user-agent.d.ts +19 -0
- package/dist/utils/user-agent.d.ts.map +1 -0
- package/dist/utils/user-agent.js +36 -0
- package/dist/utils/user-agent.js.map +1 -0
- package/dist/webhook-integration/webhook-integration.service.d.ts +5 -0
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +50 -4
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/dist/webhook-integration/webhook-protocol.registry.d.ts +6 -0
- package/dist/webhook-integration/webhook-protocol.registry.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-protocol.registry.js.map +1 -1
- package/hedhog/data/integration_provider.yaml +9 -0
- package/hedhog/data/mail.yaml +198 -62
- package/hedhog/data/mail_layout.yaml +43 -33
- package/hedhog/data/menu.yaml +2 -0
- package/hedhog/data/role.yaml +16 -0
- package/hedhog/data/route.yaml +172 -3
- package/hedhog/data/setting_group.yaml +178 -2
- package/hedhog/data/setting_subgroup.yaml +8 -0
- package/hedhog/frontend/app/_lib/hooks/use-core-access.test.tsx.ejs +125 -0
- package/hedhog/frontend/app/_lib/hooks/use-core-access.ts.ejs +29 -0
- package/hedhog/frontend/app/_lib/hooks/use-impersonation-access.test.ts.ejs +71 -0
- package/hedhog/frontend/app/_lib/hooks/use-impersonation-access.ts.ejs +32 -0
- package/hedhog/frontend/app/access-log/page.tsx.ejs +96 -22
- package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +17 -1
- package/hedhog/frontend/app/account/components/known-devices.tsx.ejs +89 -0
- package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +2 -1
- package/hedhog/frontend/app/account/components/user-devices-list.tsx.ejs +186 -0
- package/hedhog/frontend/app/account/devices/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/layout.tsx.ejs +2 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +298 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +376 -1236
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +210 -3
- package/hedhog/frontend/app/mail/template/page.tsx.ejs +190 -132
- package/hedhog/frontend/app/users/active-session.tsx.ejs +58 -11
- package/hedhog/frontend/app/users/page.tsx.ejs +69 -10
- package/hedhog/frontend/app/users/simulate-access-dialog.test.tsx.ejs +202 -0
- package/hedhog/frontend/app/users/simulate-access-dialog.tsx.ejs +170 -0
- package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +19 -0
- package/hedhog/frontend/app/users/user-devices.tsx.ejs +89 -0
- package/hedhog/frontend/app/users/user-logs.tsx.ejs +13 -1
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +46 -24
- package/hedhog/frontend/messages/en.json +146 -48
- package/hedhog/frontend/messages/pt.json +146 -48
- package/hedhog/table/access_log.yaml +7 -0
- package/hedhog/table/file.yaml +8 -0
- package/hedhog/table/mail_delivery_event.yaml +76 -0
- package/hedhog/table/mail_sent.yaml +23 -0
- package/hedhog/table/user_activity.yaml +9 -0
- package/hedhog/table/user_device.yaml +52 -0
- package/hedhog/table/user_session.yaml +20 -1
- package/package.json +6 -5
- package/src/access-log/access-log.controller.ts +1 -0
- package/src/access-log/access-log.interceptor.ts +5 -1
- package/src/access-log/access-log.service.spec.ts +33 -2
- package/src/access-log/access-log.service.ts +38 -0
- package/src/access-log/dto/query-access-log.dto.ts +5 -0
- package/src/ai/ai.controller.ts +9 -0
- package/src/ai/ai.module.ts +3 -2
- package/src/ai/ai.service.ts +150 -13
- package/src/ai/dto/richtext-edit-html.dto.ts +11 -0
- package/src/ai/richtext-ai.prompts.ts +57 -0
- package/src/ai/richtext-ai.service.ts +109 -0
- package/src/auth/auth.controller.ts +47 -8
- package/src/auth/auth.module.ts +2 -0
- package/src/auth/auth.service.spec.ts +383 -6
- package/src/auth/auth.service.ts +345 -56
- package/src/auth/dto/confirm-email.dto.ts +7 -0
- package/src/auth/dto/resend-confirmation.dto.ts +20 -0
- package/src/authorize/authorize.controller.ts +2 -0
- package/src/authorize/authorize.service.spec.ts +23 -0
- package/src/authorize/authorize.service.ts +8 -0
- package/src/challenge/challenge.service.spec.ts +73 -0
- package/src/challenge/challenge.service.ts +62 -3
- package/src/core.module.ts +19 -0
- package/src/file/file.controller.ts +54 -4
- package/src/file/file.service.spec.ts +275 -105
- package/src/file/file.service.ts +409 -14
- package/src/file/image-optimization.service.spec.ts +92 -0
- package/src/file/image-optimization.service.ts +202 -0
- package/src/file/provider/abstract.provider.ts +23 -0
- package/src/file/provider/gcs.provider.ts +17 -0
- package/src/file/provider/local.provider.ts +21 -0
- package/src/file/provider/s3.provider.ts +25 -0
- package/src/file/provider/spaces.provider.ts +26 -0
- package/src/impersonation/dto/redeem-impersonation.dto.ts +8 -0
- package/src/impersonation/dto/start-impersonation.dto.ts +13 -0
- package/src/impersonation/impersonation-restrictions.ts +84 -0
- package/src/impersonation/impersonation.controller.ts +109 -0
- package/src/impersonation/impersonation.module.ts +31 -0
- package/src/impersonation/impersonation.service.spec.ts +427 -0
- package/src/impersonation/impersonation.service.ts +577 -0
- package/src/index.ts +21 -0
- package/src/integration/services/outbox-polling.coordinator.spec.ts +90 -0
- package/src/integration/services/outbox-polling.coordinator.ts +62 -40
- package/src/integration/services/outbox.service.spec.ts +8 -0
- package/src/integration/services/outbox.service.ts +19 -13
- package/src/integration-profile/integration-profile.secrets.ts +3 -0
- package/src/integration-profile/integration-profile.utils.ts +38 -0
- package/src/integration-profile/testers/email.testers.ts +34 -1
- package/src/language/en.json +11 -0
- package/src/language/pt.json +11 -0
- package/src/mail/dto/send.dto.ts +46 -2
- package/src/mail/mail-template.spec.ts +79 -2
- package/src/mail/mail.module.ts +2 -0
- package/src/mail/mail.service.spec.ts +307 -6
- package/src/mail/mail.service.ts +227 -30
- package/src/mail-delivery/mail-delivery-provision.service.ts +66 -0
- package/src/mail-delivery/mail-delivery.constants.ts +31 -0
- package/src/mail-delivery/mail-delivery.controller.ts +38 -0
- package/src/mail-delivery/mail-delivery.module.ts +23 -0
- package/src/mail-delivery/mail-delivery.service.spec.ts +136 -0
- package/src/mail-delivery/mail-delivery.service.ts +152 -0
- package/src/mail-delivery/ses-event.parser.spec.ts +140 -0
- package/src/mail-delivery/ses-event.parser.ts +191 -0
- package/src/mail-delivery/ses-webhook.service.spec.ts +170 -0
- package/src/mail-delivery/ses-webhook.service.ts +240 -0
- package/src/mail-delivery/sns-message.util.spec.ts +163 -0
- package/src/mail-delivery/sns-message.util.ts +125 -0
- package/src/mail-sent/dto/query-mail-sent-list.dto.ts +15 -0
- package/src/mail-sent/mail-sent.service.ts +46 -2
- package/src/mailbox/mailbox-fetcher.registry.ts +132 -0
- package/src/mailbox/mailbox.module.ts +14 -0
- package/src/meeting/meeting-provider.registry.ts +193 -0
- package/src/meeting/meeting.constants.ts +53 -0
- package/src/meeting/meeting.module.ts +13 -0
- package/src/middlewares/locale-injection.middleware.ts +25 -6
- package/src/oauth/providers/google.provider.ts +49 -15
- package/src/observability/monitored-cron.spec.ts +102 -0
- package/src/observability/monitored-cron.ts +110 -0
- package/src/pairing/dto/approve-reverse-pairing.dto.ts +20 -0
- package/src/pairing/dto/claim-pairing.dto.ts +19 -0
- package/src/pairing/dto/confirm-pairing.dto.ts +14 -0
- package/src/pairing/dto/poll-reverse-pairing.dto.ts +9 -0
- package/src/pairing/dto/start-pairing.dto.ts +19 -0
- package/src/pairing/dto/start-reverse-pairing.dto.ts +45 -0
- package/src/pairing/pairing.controller.ts +246 -0
- package/src/pairing/pairing.module.ts +27 -0
- package/src/pairing/pairing.service.spec.ts +462 -0
- package/src/pairing/pairing.service.ts +614 -0
- package/src/pdf/pdf.service.ts +6 -24
- package/src/profile/profile.service.ts +26 -8
- package/src/redis/redis-pubsub.ts +11 -0
- package/src/role/guards/role.guard.spec.ts +75 -0
- package/src/role/guards/role.guard.ts +64 -46
- package/src/session/session.service.spec.ts +81 -0
- package/src/session/session.service.ts +93 -6
- package/src/setting/brand-asset.service.ts +137 -0
- package/src/setting/setting.module.ts +3 -2
- package/src/setting/setting.service.spec.ts +79 -1
- package/src/setting/setting.service.ts +109 -1
- package/src/token/token.service.spec.ts +62 -0
- package/src/token/token.service.ts +57 -3
- package/src/user/user.service.spec.ts +33 -2
- package/src/user/user.service.ts +108 -6
- package/src/user-device/user-device.controller.ts +77 -0
- package/src/user-device/user-device.module.ts +13 -0
- package/src/user-device/user-device.service.spec.ts +234 -0
- package/src/user-device/user-device.service.ts +168 -0
- package/src/utils/device-key.spec.ts +57 -0
- package/src/utils/device-key.ts +32 -0
- package/src/utils/geo-ip.spec.ts +134 -0
- package/src/utils/geo-ip.ts +93 -0
- package/src/utils/locale-context.spec.ts +27 -1
- package/src/utils/locale-context.ts +15 -12
- package/src/utils/security-event-vars.spec.ts +161 -0
- package/src/utils/security-event-vars.ts +153 -0
- package/src/utils/user-agent.spec.ts +37 -0
- package/src/utils/user-agent.ts +46 -0
- package/src/webhook-integration/webhook-integration.service.ts +57 -3
- package/src/webhook-integration/webhook-protocol.registry.ts +6 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Role, User } from '@hed-hog/api';
|
|
2
|
+
import { Pagination, PaginationDTO } from '@hed-hog/api-pagination';
|
|
3
|
+
import {
|
|
4
|
+
Controller,
|
|
5
|
+
Delete,
|
|
6
|
+
Get,
|
|
7
|
+
Headers,
|
|
8
|
+
Param,
|
|
9
|
+
ParseIntPipe,
|
|
10
|
+
} from '@nestjs/common';
|
|
11
|
+
import { UserDeviceService } from './user-device.service';
|
|
12
|
+
|
|
13
|
+
@Role()
|
|
14
|
+
@Controller('user-device')
|
|
15
|
+
export class UserDeviceController {
|
|
16
|
+
constructor(private readonly userDeviceService: UserDeviceService) {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Dispositivos do proprio usuario autenticado.
|
|
20
|
+
*
|
|
21
|
+
* Os headers da requisicao entram para que o service saiba qual das linhas e
|
|
22
|
+
* o aparelho que esta perguntando (`current`) — sem eles a tela nao consegue
|
|
23
|
+
* distinguir "este dispositivo" dos outros, e o unico jeito de fazer isso no
|
|
24
|
+
* cliente seria expor a device_key.
|
|
25
|
+
*/
|
|
26
|
+
@Get()
|
|
27
|
+
async list(
|
|
28
|
+
@Pagination() paginationParams: PaginationDTO,
|
|
29
|
+
@User() { id },
|
|
30
|
+
@Headers('user-agent') userAgent: string,
|
|
31
|
+
@Headers('x-browser-id') browserId: string,
|
|
32
|
+
) {
|
|
33
|
+
return this.userDeviceService.list(paginationParams, id, {
|
|
34
|
+
userAgent: userAgent || null,
|
|
35
|
+
browserId: browserId || null,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Esquece um dispositivo do proprio usuario.
|
|
41
|
+
*
|
|
42
|
+
* O id do usuario vem do token, nunca da URL: e o que impede alguem de apagar
|
|
43
|
+
* o dispositivo de outra pessoa chutando o id.
|
|
44
|
+
*/
|
|
45
|
+
@Delete(':deviceId')
|
|
46
|
+
async forget(
|
|
47
|
+
@User() { id },
|
|
48
|
+
@Param('deviceId', ParseIntPipe) deviceId: number,
|
|
49
|
+
) {
|
|
50
|
+
return this.userDeviceService.forget(id, deviceId);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Dispositivos de outro usuario, para o sheet do admin. Restrita ao papel
|
|
55
|
+
* admin pela tabela `route` (ver hedhog/data/route.yaml) — o RoleGuard e quem
|
|
56
|
+
* barra; a rota em si nao checa papel.
|
|
57
|
+
*/
|
|
58
|
+
@Get('user/:userId')
|
|
59
|
+
async listForUser(
|
|
60
|
+
@Pagination() paginationParams: PaginationDTO,
|
|
61
|
+
@Param('userId', ParseIntPipe) userId: number,
|
|
62
|
+
) {
|
|
63
|
+
return this.userDeviceService.list(paginationParams, userId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Esquece um dispositivo de outro usuario. Mesma restricao de papel da
|
|
68
|
+
* listagem acima.
|
|
69
|
+
*/
|
|
70
|
+
@Delete('user/:userId/:deviceId')
|
|
71
|
+
async forgetForUser(
|
|
72
|
+
@Param('userId', ParseIntPipe) userId: number,
|
|
73
|
+
@Param('deviceId', ParseIntPipe) deviceId: number,
|
|
74
|
+
) {
|
|
75
|
+
return this.userDeviceService.forget(userId, deviceId);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PaginationModule } from '@hed-hog/api-pagination';
|
|
2
|
+
import { PrismaModule } from '@hed-hog/api-prisma';
|
|
3
|
+
import { forwardRef, Module } from '@nestjs/common';
|
|
4
|
+
import { UserDeviceController } from './user-device.controller';
|
|
5
|
+
import { UserDeviceService } from './user-device.service';
|
|
6
|
+
|
|
7
|
+
@Module({
|
|
8
|
+
imports: [forwardRef(() => PrismaModule), forwardRef(() => PaginationModule)],
|
|
9
|
+
controllers: [UserDeviceController],
|
|
10
|
+
providers: [UserDeviceService],
|
|
11
|
+
exports: [UserDeviceService],
|
|
12
|
+
})
|
|
13
|
+
export class UserDeviceModule {}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
2
|
+
import { buildDeviceKey } from '../utils/device-key';
|
|
3
|
+
import { UserDeviceService } from './user-device.service';
|
|
4
|
+
|
|
5
|
+
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/151.0.0.0 Safari/537.36';
|
|
6
|
+
|
|
7
|
+
const makeService = () => {
|
|
8
|
+
const prisma = {
|
|
9
|
+
user_device: {
|
|
10
|
+
findFirst: jest.fn(),
|
|
11
|
+
findUnique: jest.fn(),
|
|
12
|
+
upsert: jest.fn(),
|
|
13
|
+
deleteMany: jest.fn(),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
const pagination = { paginatePrismaModel: jest.fn() };
|
|
17
|
+
return {
|
|
18
|
+
service: new UserDeviceService(prisma as any, pagination as any),
|
|
19
|
+
prisma,
|
|
20
|
+
pagination,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('UserDeviceService.isNewDevice', () => {
|
|
25
|
+
it('nao considera novo o primeiro login da conta', async () => {
|
|
26
|
+
const { service, prisma } = makeService();
|
|
27
|
+
prisma.user_device.findFirst.mockResolvedValue(null);
|
|
28
|
+
prisma.user_device.findUnique.mockResolvedValue(null);
|
|
29
|
+
|
|
30
|
+
expect(await service.isNewDevice(1, UA, 'b1')).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('considera novo quando ha dispositivos, mas nenhum e este', async () => {
|
|
34
|
+
const { service, prisma } = makeService();
|
|
35
|
+
prisma.user_device.findFirst.mockResolvedValue({ id: 7 });
|
|
36
|
+
prisma.user_device.findUnique.mockResolvedValue(null);
|
|
37
|
+
|
|
38
|
+
expect(await service.isNewDevice(1, UA, 'b1')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('nao considera novo um dispositivo ja registrado', async () => {
|
|
42
|
+
const { service, prisma } = makeService();
|
|
43
|
+
prisma.user_device.findFirst.mockResolvedValue({ id: 7 });
|
|
44
|
+
prisma.user_device.findUnique.mockResolvedValue({ id: 9 });
|
|
45
|
+
|
|
46
|
+
expect(await service.isNewDevice(1, UA, 'b1')).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('procura pela device_key derivada do browser_id', async () => {
|
|
50
|
+
const { service, prisma } = makeService();
|
|
51
|
+
prisma.user_device.findFirst.mockResolvedValue(null);
|
|
52
|
+
prisma.user_device.findUnique.mockResolvedValue(null);
|
|
53
|
+
|
|
54
|
+
await service.isNewDevice(42, UA, 'browser-1');
|
|
55
|
+
|
|
56
|
+
expect(prisma.user_device.findUnique).toHaveBeenCalledWith(
|
|
57
|
+
expect.objectContaining({
|
|
58
|
+
where: {
|
|
59
|
+
user_id_device_key: { user_id: 42, device_key: buildDeviceKey(UA, 'browser-1') },
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('nao reanuncia o dispositivo quando o navegador se atualiza', async () => {
|
|
66
|
+
const { service, prisma } = makeService();
|
|
67
|
+
prisma.user_device.findFirst.mockResolvedValue({ id: 7 });
|
|
68
|
+
// Registrado com o Chrome 150; volta com o 151, mesmo browser_id.
|
|
69
|
+
prisma.user_device.findUnique.mockImplementation(async ({ where }: any) =>
|
|
70
|
+
where.user_id_device_key.device_key === buildDeviceKey('Chrome/150', 'b1')
|
|
71
|
+
? { id: 9 }
|
|
72
|
+
: null,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(await service.isNewDevice(1, 'Chrome/151', 'b1')).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('UserDeviceService.remember', () => {
|
|
80
|
+
beforeEach(() => jest.clearAllMocks());
|
|
81
|
+
|
|
82
|
+
it('grava a device_key, o UA interpretado e o ultimo IP', async () => {
|
|
83
|
+
const { service, prisma } = makeService();
|
|
84
|
+
|
|
85
|
+
await service.remember(42, { userAgent: UA, browserId: 'b1', ipAddress: '8.8.8.8' });
|
|
86
|
+
|
|
87
|
+
const arg = prisma.user_device.upsert.mock.calls[0][0] as any;
|
|
88
|
+
expect(arg.where).toEqual({
|
|
89
|
+
user_id_device_key: { user_id: 42, device_key: buildDeviceKey(UA, 'b1') },
|
|
90
|
+
});
|
|
91
|
+
expect(arg.create).toMatchObject({
|
|
92
|
+
user_id: 42,
|
|
93
|
+
browser_id: 'b1',
|
|
94
|
+
browser_name: 'Chrome',
|
|
95
|
+
os_name: 'Windows',
|
|
96
|
+
last_ip: '8.8.8.8',
|
|
97
|
+
});
|
|
98
|
+
expect(arg.update.last_seen_at).toEqual(expect.any(Date));
|
|
99
|
+
// O create nao pode reescrever o created_at de um dispositivo ja conhecido.
|
|
100
|
+
expect(arg.update).not.toHaveProperty('created_at');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('normaliza vazios para null em vez de gravar string vazia', async () => {
|
|
104
|
+
const { service, prisma } = makeService();
|
|
105
|
+
|
|
106
|
+
await service.remember(42, { userAgent: '', browserId: ' ', ipAddress: undefined });
|
|
107
|
+
|
|
108
|
+
const arg = prisma.user_device.upsert.mock.calls[0][0] as any;
|
|
109
|
+
expect(arg.create).toMatchObject({
|
|
110
|
+
browser_id: null,
|
|
111
|
+
user_agent: null,
|
|
112
|
+
browser_name: null,
|
|
113
|
+
os_name: null,
|
|
114
|
+
last_ip: null,
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('engole a falha: nao registrar dispositivo nao pode impedir o login', async () => {
|
|
119
|
+
const { service, prisma } = makeService();
|
|
120
|
+
prisma.user_device.upsert.mockRejectedValue(new Error('deadlock'));
|
|
121
|
+
|
|
122
|
+
await expect(
|
|
123
|
+
service.remember(42, { userAgent: UA, browserId: 'b1', ipAddress: '8.8.8.8' }),
|
|
124
|
+
).resolves.toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('UserDeviceService.list', () => {
|
|
129
|
+
beforeEach(() => jest.clearAllMocks());
|
|
130
|
+
|
|
131
|
+
const row = (id: number, device_key: string) => ({
|
|
132
|
+
id,
|
|
133
|
+
device_key,
|
|
134
|
+
browser_name: 'Chrome',
|
|
135
|
+
os_name: 'Windows',
|
|
136
|
+
user_agent: UA,
|
|
137
|
+
last_ip: '8.8.8.8',
|
|
138
|
+
last_seen_at: new Date(),
|
|
139
|
+
created_at: new Date(),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('pagina escopado ao usuário e do mais recente para o mais antigo', async () => {
|
|
143
|
+
const { service, pagination, prisma } = makeService();
|
|
144
|
+
pagination.paginatePrismaModel.mockResolvedValue({ data: [], total: 0 });
|
|
145
|
+
|
|
146
|
+
await service.list({ page: 1, pageSize: 10 } as any, 42);
|
|
147
|
+
|
|
148
|
+
const [model, args] = pagination.paginatePrismaModel.mock.calls[0] as any[];
|
|
149
|
+
expect(model).toBe(prisma.user_device);
|
|
150
|
+
expect(args.where).toEqual({ user_id: 42 });
|
|
151
|
+
// Sem isto a primeira página traz os dispositivos mais antigos.
|
|
152
|
+
expect(args.orderBy).toEqual({ last_seen_at: 'desc' });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('não expõe a device_key, que identifica o dispositivo nas requisições', async () => {
|
|
156
|
+
const { service, pagination } = makeService();
|
|
157
|
+
// Ela é lida do banco para calcular o `current`, mas não pode sair daqui.
|
|
158
|
+
pagination.paginatePrismaModel.mockResolvedValue({
|
|
159
|
+
data: [row(1, buildDeviceKey(UA, 'b1'))],
|
|
160
|
+
total: 1,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const { data } = await service.list({ page: 1, pageSize: 10 } as any, 42, {
|
|
164
|
+
userAgent: UA,
|
|
165
|
+
browserId: 'b1',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(data[0]).not.toHaveProperty('device_key');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('marca como atual só o dispositivo que fez a chamada', async () => {
|
|
172
|
+
const { service, pagination } = makeService();
|
|
173
|
+
pagination.paginatePrismaModel.mockResolvedValue({
|
|
174
|
+
data: [row(1, buildDeviceKey(UA, 'b1')), row(2, buildDeviceKey(UA, 'b2'))],
|
|
175
|
+
total: 2,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const { data } = await service.list({ page: 1, pageSize: 10 } as any, 42, {
|
|
179
|
+
userAgent: UA,
|
|
180
|
+
browserId: 'b2',
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(data.map((d: any) => [d.id, d.current])).toEqual([
|
|
184
|
+
[1, false],
|
|
185
|
+
[2, true],
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('sem fingerprint nenhum dispositivo é o atual', async () => {
|
|
190
|
+
const { service, pagination } = makeService();
|
|
191
|
+
pagination.paginatePrismaModel.mockResolvedValue({
|
|
192
|
+
data: [row(1, buildDeviceKey(UA, 'b1'))],
|
|
193
|
+
total: 1,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// O caso do admin olhando a lista de outra pessoa: sem os headers da
|
|
197
|
+
// requisição, `buildDeviceKey(null, null)` casaria com as linhas gravadas
|
|
198
|
+
// sem UA nem browser_id se o flag não fosse desligado de propósito.
|
|
199
|
+
const { data } = await service.list({ page: 1, pageSize: 10 } as any, 42);
|
|
200
|
+
|
|
201
|
+
expect(data[0].current).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('UserDeviceService.forget', () => {
|
|
206
|
+
beforeEach(() => jest.clearAllMocks());
|
|
207
|
+
|
|
208
|
+
it('exige que o dispositivo seja do usuário informado', async () => {
|
|
209
|
+
const { service, prisma } = makeService();
|
|
210
|
+
prisma.user_device.deleteMany.mockResolvedValue({ count: 1 });
|
|
211
|
+
|
|
212
|
+
await service.forget(42, 7);
|
|
213
|
+
|
|
214
|
+
// O user_id no where é o que impede apagar o dispositivo de outra pessoa
|
|
215
|
+
// só por adivinhar o id.
|
|
216
|
+
expect(prisma.user_device.deleteMany).toHaveBeenCalledWith({
|
|
217
|
+
where: { id: 7, user_id: 42 },
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('devolve deleted=false quando o dispositivo é de outro usuário', async () => {
|
|
222
|
+
const { service, prisma } = makeService();
|
|
223
|
+
prisma.user_device.deleteMany.mockResolvedValue({ count: 0 });
|
|
224
|
+
|
|
225
|
+
await expect(service.forget(42, 7)).resolves.toEqual({ deleted: false });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('devolve deleted=true quando removeu', async () => {
|
|
229
|
+
const { service, prisma } = makeService();
|
|
230
|
+
prisma.user_device.deleteMany.mockResolvedValue({ count: 1 });
|
|
231
|
+
|
|
232
|
+
await expect(service.forget(42, 7)).resolves.toEqual({ deleted: true });
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
2
|
+
import { PrismaService } from '@hed-hog/api-prisma';
|
|
3
|
+
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
|
4
|
+
import { buildDeviceKey } from '../utils/device-key';
|
|
5
|
+
import { parseUserAgent } from '../utils/user-agent';
|
|
6
|
+
|
|
7
|
+
export interface DeviceFingerprint {
|
|
8
|
+
userAgent?: string | null;
|
|
9
|
+
browserId?: string | null;
|
|
10
|
+
ipAddress?: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Dispositivos ja vistos por usuario.
|
|
15
|
+
*
|
|
16
|
+
* Substitui a heuristica anterior, que procurava no historico de `user_session`
|
|
17
|
+
* para decidir se um login vinha de um dispositivo novo. Aquilo dependia de
|
|
18
|
+
* dados que a rotina de limpeza apaga: `clearExpiredSessions` remove sessoes
|
|
19
|
+
* expiradas com mais de `session-retention-days`, entao um dispositivo parado
|
|
20
|
+
* alguns meses saia do historico e era anunciado como novo de novo.
|
|
21
|
+
*/
|
|
22
|
+
@Injectable()
|
|
23
|
+
export class UserDeviceService {
|
|
24
|
+
private readonly logger = new Logger(UserDeviceService.name);
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly prisma: PrismaService,
|
|
28
|
+
@Inject(forwardRef(() => PaginationService))
|
|
29
|
+
private readonly paginationService: PaginationService,
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `true` quando o usuario ja tem dispositivos registrados e nenhum deles e
|
|
34
|
+
* este.
|
|
35
|
+
*
|
|
36
|
+
* O primeiro login da conta nunca conta como dispositivo novo: nao ha com o
|
|
37
|
+
* que comparar, e quem acabou de se cadastrar ja recebeu as boas-vindas.
|
|
38
|
+
*
|
|
39
|
+
* Tem que rodar ANTES de `remember`, que passa a conhecer o dispositivo atual.
|
|
40
|
+
*/
|
|
41
|
+
async isNewDevice(
|
|
42
|
+
userId: number,
|
|
43
|
+
userAgent?: string | null,
|
|
44
|
+
browserId?: string | null,
|
|
45
|
+
): Promise<boolean> {
|
|
46
|
+
const device_key = buildDeviceKey(userAgent, browserId);
|
|
47
|
+
|
|
48
|
+
const [anyDevice, knownDevice] = await Promise.all([
|
|
49
|
+
this.prisma.user_device.findFirst({
|
|
50
|
+
where: { user_id: userId },
|
|
51
|
+
select: { id: true },
|
|
52
|
+
}),
|
|
53
|
+
this.prisma.user_device.findUnique({
|
|
54
|
+
where: { user_id_device_key: { user_id: userId, device_key } },
|
|
55
|
+
select: { id: true },
|
|
56
|
+
}),
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
return Boolean(anyDevice) && !knownDevice;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Registra o dispositivo ou atualiza o ultimo uso.
|
|
64
|
+
*
|
|
65
|
+
* Nunca lanca: isto roda dentro do fluxo de login, e falhar em anotar um
|
|
66
|
+
* dispositivo nao pode impedir alguem de entrar. O custo de engolir o erro e
|
|
67
|
+
* um alerta repetido no proximo login, nao um login perdido.
|
|
68
|
+
*/
|
|
69
|
+
async remember(
|
|
70
|
+
userId: number,
|
|
71
|
+
{ userAgent, browserId, ipAddress }: DeviceFingerprint,
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
const device_key = buildDeviceKey(userAgent, browserId);
|
|
74
|
+
const ua = parseUserAgent(userAgent);
|
|
75
|
+
|
|
76
|
+
const seen = {
|
|
77
|
+
browser_id: (browserId ?? '').trim() || null,
|
|
78
|
+
user_agent: (userAgent ?? '').trim() || null,
|
|
79
|
+
browser_name: ua?.browser_name ?? null,
|
|
80
|
+
os_name: ua?.os_name ?? null,
|
|
81
|
+
last_ip: (ipAddress ?? '').trim() || null,
|
|
82
|
+
last_seen_at: new Date(),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await this.prisma.user_device.upsert({
|
|
87
|
+
where: { user_id_device_key: { user_id: userId, device_key } },
|
|
88
|
+
create: { user_id: userId, device_key, ...seen },
|
|
89
|
+
update: seen,
|
|
90
|
+
});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
this.logger.error(
|
|
93
|
+
`Falha ao registrar dispositivo userId=${userId}: ${
|
|
94
|
+
error instanceof Error ? error.message : String(error)
|
|
95
|
+
}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Dispositivos de um usuario, do mais recente para o mais antigo.
|
|
102
|
+
*
|
|
103
|
+
* Paginado como a lista de sessoes: uma conta antiga acumula dezenas de
|
|
104
|
+
* dispositivos, e devolver tudo de uma vez entrega uma parede de linhas.
|
|
105
|
+
*
|
|
106
|
+
* A `device_key` e lida para marcar qual linha e o aparelho que fez esta
|
|
107
|
+
* chamada (`current`), mas nunca sai daqui: e derivada do browser_id, entao
|
|
108
|
+
* devolve-la seria entregar de graca o valor que identifica o dispositivo nas
|
|
109
|
+
* requisicoes. Sem `fingerprint` — o caso do admin olhando a lista de outra
|
|
110
|
+
* pessoa — nenhuma linha e a atual.
|
|
111
|
+
*/
|
|
112
|
+
async list(
|
|
113
|
+
paginationParams: PaginationDTO,
|
|
114
|
+
userId: number,
|
|
115
|
+
fingerprint?: DeviceFingerprint,
|
|
116
|
+
) {
|
|
117
|
+
const currentKey = fingerprint
|
|
118
|
+
? buildDeviceKey(fingerprint.userAgent, fingerprint.browserId)
|
|
119
|
+
: null;
|
|
120
|
+
|
|
121
|
+
const paginate = await this.paginationService.paginatePrismaModel(
|
|
122
|
+
this.prisma.user_device,
|
|
123
|
+
{
|
|
124
|
+
...paginationParams,
|
|
125
|
+
where: { user_id: userId },
|
|
126
|
+
select: {
|
|
127
|
+
id: true,
|
|
128
|
+
device_key: true,
|
|
129
|
+
browser_name: true,
|
|
130
|
+
os_name: true,
|
|
131
|
+
user_agent: true,
|
|
132
|
+
last_ip: true,
|
|
133
|
+
last_seen_at: true,
|
|
134
|
+
created_at: true,
|
|
135
|
+
},
|
|
136
|
+
// Explicito: sem isto a paginacao ordena crescente e a primeira pagina
|
|
137
|
+
// mostra os dispositivos mais antigos.
|
|
138
|
+
orderBy: { last_seen_at: 'desc' },
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
...paginate,
|
|
144
|
+
data: (paginate.data ?? []).map(
|
|
145
|
+
({ device_key, ...device }: { device_key: string }) => ({
|
|
146
|
+
...device,
|
|
147
|
+
current: Boolean(currentKey) && device_key === currentKey,
|
|
148
|
+
}),
|
|
149
|
+
),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Esquece um dispositivo. O proximo login vindo dele volta a disparar o
|
|
155
|
+
* alerta de dispositivo desconhecido — que e a reacao certa para "esse ai nao
|
|
156
|
+
* fui eu".
|
|
157
|
+
*
|
|
158
|
+
* O `user_id` entra no where junto com o id: sem ele, qualquer usuario
|
|
159
|
+
* autenticado apagaria o dispositivo de qualquer outro so por adivinhar o id.
|
|
160
|
+
*/
|
|
161
|
+
async forget(userId: number, deviceId: number): Promise<{ deleted: boolean }> {
|
|
162
|
+
const { count } = await this.prisma.user_device.deleteMany({
|
|
163
|
+
where: { id: deviceId, user_id: userId },
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return { deleted: count > 0 };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import { buildDeviceKey } from './device-key';
|
|
4
|
+
|
|
5
|
+
const sha256 = (s: string) => createHash('sha256').update(s, 'utf8').digest('hex');
|
|
6
|
+
|
|
7
|
+
const UA_A = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/151.0.0.0';
|
|
8
|
+
const UA_B = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605.1.15';
|
|
9
|
+
|
|
10
|
+
describe('buildDeviceKey', () => {
|
|
11
|
+
it('e estavel para a mesma entrada', () => {
|
|
12
|
+
expect(buildDeviceKey(UA_A, 'abc')).toBe(buildDeviceKey(UA_A, 'abc'));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('devolve sempre 64 hex', () => {
|
|
16
|
+
for (const key of [
|
|
17
|
+
buildDeviceKey(UA_A, 'abc'),
|
|
18
|
+
buildDeviceKey(UA_A, null),
|
|
19
|
+
buildDeviceKey(null, null),
|
|
20
|
+
]) {
|
|
21
|
+
expect(key).toMatch(/^[0-9a-f]{64}$/);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('da precedencia ao browser_id e ignora o user agent', () => {
|
|
26
|
+
// Mesmo dispositivo que atualizou o navegador continua sendo o mesmo.
|
|
27
|
+
expect(buildDeviceKey(UA_A, 'abc')).toBe(buildDeviceKey(UA_B, 'abc'));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('separa browser_ids diferentes', () => {
|
|
31
|
+
expect(buildDeviceKey(UA_A, 'abc')).not.toBe(buildDeviceKey(UA_A, 'xyz'));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('cai no user agent quando nao ha browser_id', () => {
|
|
35
|
+
expect(buildDeviceKey(UA_A, null)).not.toBe(buildDeviceKey(UA_B, null));
|
|
36
|
+
expect(buildDeviceKey(UA_A, null)).toBe(buildDeviceKey(UA_A, undefined));
|
|
37
|
+
expect(buildDeviceKey(UA_A, ' ')).toBe(buildDeviceKey(UA_A, null));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('nao confunde um user agent com um browser_id de mesmo texto', () => {
|
|
41
|
+
// E para isso que servem os prefixos b:/u:.
|
|
42
|
+
expect(buildDeviceKey('token-123', null)).not.toBe(buildDeviceKey(null, 'token-123'));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('normaliza espacos nas bordas', () => {
|
|
46
|
+
expect(buildDeviceKey(UA_A, ' abc ')).toBe(buildDeviceKey(UA_A, 'abc'));
|
|
47
|
+
expect(buildDeviceKey(` ${UA_A} `, null)).toBe(buildDeviceKey(UA_A, null));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('bate com a expressao que a migration usa no backfill', () => {
|
|
51
|
+
// A migration 20260808140000_core_user_device monta a mesma string antes do
|
|
52
|
+
// sha256. Divergir aqui faz todo dispositivo conhecido virar novo.
|
|
53
|
+
expect(buildDeviceKey(UA_A, 'abc')).toBe(sha256('b:abc'));
|
|
54
|
+
expect(buildDeviceKey(UA_A, null)).toBe(sha256(`u:${UA_A}`));
|
|
55
|
+
expect(buildDeviceKey(null, null)).toBe(sha256('u:'));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Identidade estavel de um dispositivo para um usuario.
|
|
5
|
+
*
|
|
6
|
+
* O `browser_id` (UUID em localStorage, header X-Browser-ID) e o sinal forte e
|
|
7
|
+
* tem precedencia. Sem ele — app desktop, integracoes, clientes que nao mandam
|
|
8
|
+
* o header — cai no User-Agent, que erra para menos (nao distingue dois
|
|
9
|
+
* dispositivos com o mesmo UA) e nunca para mais.
|
|
10
|
+
*
|
|
11
|
+
* O resultado e um sha256 hex de largura fixa, e nao o valor cru, para caber
|
|
12
|
+
* num indice unico junto com o user_id sem depender do tamanho do UA. O
|
|
13
|
+
* `browser_id` e o `user_agent` continuam gravados em colunas proprias de
|
|
14
|
+
* `user_device`, entao nada de diagnostico se perde no hash.
|
|
15
|
+
*
|
|
16
|
+
* O prefixo `b:`/`u:` impede que um User-Agent que por acaso seja igual a um
|
|
17
|
+
* browser_id colida com ele.
|
|
18
|
+
*
|
|
19
|
+
* IMPORTANTE: a migration 20260808140000_core_user_device reproduz esta mesma
|
|
20
|
+
* expressao em SQL para popular a tabela a partir do historico de sessoes. Ao
|
|
21
|
+
* mudar a normalizacao aqui, as chaves ja gravadas deixam de casar e todo
|
|
22
|
+
* dispositivo conhecido vira "novo" — o que dispara um alerta para cada usuario.
|
|
23
|
+
*/
|
|
24
|
+
export function buildDeviceKey(
|
|
25
|
+
userAgent?: string | null,
|
|
26
|
+
browserId?: string | null,
|
|
27
|
+
): string {
|
|
28
|
+
const browser = (browserId ?? '').trim();
|
|
29
|
+
const source = browser ? `b:${browser}` : `u:${(userAgent ?? '').trim()}`;
|
|
30
|
+
|
|
31
|
+
return createHash('sha256').update(source, 'utf8').digest('hex');
|
|
32
|
+
}
|