@hed-hog/core 0.0.387 → 0.0.390
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 +12 -11
- package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
- package/dist/access-log/access-log.interceptor.js +4 -2
- package/dist/access-log/access-log.interceptor.js.map +1 -1
- package/dist/ai/ai-attachment.fragments.d.ts +23 -0
- package/dist/ai/ai-attachment.fragments.d.ts.map +1 -0
- package/dist/ai/ai-attachment.fragments.js +34 -0
- package/dist/ai/ai-attachment.fragments.js.map +1 -0
- package/dist/ai/ai.controller.d.ts +7 -16
- package/dist/ai/ai.controller.d.ts.map +1 -1
- package/dist/ai/ai.prompts.d.ts +16 -0
- package/dist/ai/ai.prompts.d.ts.map +1 -0
- package/dist/ai/ai.prompts.js +37 -0
- package/dist/ai/ai.prompts.js.map +1 -0
- package/dist/ai/ai.service.d.ts +63 -12
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +215 -88
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/ai/dto/chat.dto.d.ts +4 -1
- package/dist/ai/dto/chat.dto.d.ts.map +1 -1
- package/dist/ai/dto/chat.dto.js +23 -1
- package/dist/ai/dto/chat.dto.js.map +1 -1
- package/dist/ai/dto/create-agent.dto.d.ts +1 -1
- package/dist/ai/dto/create-agent.dto.d.ts.map +1 -1
- package/dist/ai/dto/create-agent.dto.js +1 -1
- package/dist/ai/dto/create-agent.dto.js.map +1 -1
- package/dist/ai/dto/update-agent.dto.d.ts +1 -1
- package/dist/ai/dto/update-agent.dto.d.ts.map +1 -1
- package/dist/ai/dto/update-agent.dto.js +1 -1
- package/dist/ai/dto/update-agent.dto.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.controller.d.ts +61 -7
- package/dist/ai-instruction/ai-instruction.controller.d.ts.map +1 -1
- package/dist/ai-instruction/ai-instruction.controller.js +63 -20
- package/dist/ai-instruction/ai-instruction.controller.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.module.d.ts.map +1 -1
- package/dist/ai-instruction/ai-instruction.module.js +13 -2
- package/dist/ai-instruction/ai-instruction.module.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.service.d.ts +131 -6
- package/dist/ai-instruction/ai-instruction.service.d.ts.map +1 -1
- package/dist/ai-instruction/ai-instruction.service.js +533 -60
- package/dist/ai-instruction/ai-instruction.service.js.map +1 -1
- package/dist/ai-instruction/ai-prompt-builder.service.d.ts +8 -0
- package/dist/ai-instruction/ai-prompt-builder.service.d.ts.map +1 -1
- package/dist/ai-instruction/ai-prompt-builder.service.js +10 -1
- package/dist/ai-instruction/ai-prompt-builder.service.js.map +1 -1
- package/dist/ai-instruction/ai-prompt-registry.d.ts +45 -0
- package/dist/ai-instruction/ai-prompt-registry.d.ts.map +1 -0
- package/dist/ai-instruction/ai-prompt-registry.js +49 -0
- package/dist/ai-instruction/ai-prompt-registry.js.map +1 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.d.ts +89 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.d.ts.map +1 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.js +222 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.js.map +1 -0
- package/dist/ai-instruction/ai-prompt-template.util.d.ts +70 -0
- package/dist/ai-instruction/ai-prompt-template.util.d.ts.map +1 -0
- package/dist/ai-instruction/ai-prompt-template.util.js +154 -0
- package/dist/ai-instruction/ai-prompt-template.util.js.map +1 -0
- package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts +5 -0
- package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts.map +1 -1
- package/dist/ai-instruction/dto/create-ai-instruction.dto.js +27 -1
- package/dist/ai-instruction/dto/create-ai-instruction.dto.js.map +1 -1
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts +14 -0
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts.map +1 -0
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.js +42 -0
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.js.map +1 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts +11 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts.map +1 -1
- package/dist/ai-instruction/dto/update-ai-instruction.dto.js +34 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.js.map +1 -1
- package/dist/ai-instruction/types/ai-instruction.types.d.ts +13 -1
- package/dist/ai-instruction/types/ai-instruction.types.d.ts.map +1 -1
- package/dist/ai-instruction/types/ai-instruction.types.js +10 -0
- package/dist/ai-instruction/types/ai-instruction.types.js.map +1 -1
- package/dist/auth/auth.controller.d.ts +24 -9
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +49 -21
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +33 -8
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +78 -29
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/captcha/captcha.module.d.ts +3 -0
- package/dist/captcha/captcha.module.d.ts.map +1 -0
- package/dist/captcha/captcha.module.js +23 -0
- package/dist/captcha/captcha.module.js.map +1 -0
- package/dist/captcha/captcha.service.d.ts +38 -0
- package/dist/captcha/captcha.service.d.ts.map +1 -0
- package/dist/captcha/captcha.service.js +144 -0
- package/dist/captcha/captcha.service.js.map +1 -0
- package/dist/challenge/challenge.service.d.ts +16 -7
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +64 -16
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/core.module.d.ts +3 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +29 -2
- package/dist/core.module.js.map +1 -1
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +15 -15
- package/dist/dashboard/dashboard/dashboard.service.d.ts +15 -15
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +6 -6
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +6 -6
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +20 -20
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +20 -20
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +13 -13
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +13 -13
- package/dist/file/file.service.d.ts +6 -0
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +44 -4
- package/dist/file/file.service.js.map +1 -1
- package/dist/incident/incident-notifier.service.d.ts +36 -0
- package/dist/incident/incident-notifier.service.d.ts.map +1 -0
- package/dist/incident/incident-notifier.service.js +208 -0
- package/dist/incident/incident-notifier.service.js.map +1 -0
- package/dist/incident/incident.controller.d.ts +23 -0
- package/dist/incident/incident.controller.d.ts.map +1 -0
- package/dist/incident/incident.controller.js +75 -0
- package/dist/incident/incident.controller.js.map +1 -0
- package/dist/incident/incident.module.d.ts +7 -0
- package/dist/incident/incident.module.d.ts.map +1 -0
- package/dist/incident/incident.module.js +32 -0
- package/dist/incident/incident.module.js.map +1 -0
- package/dist/incident/incident.service.d.ts +72 -0
- package/dist/incident/incident.service.d.ts.map +1 -0
- package/dist/incident/incident.service.js +184 -0
- package/dist/incident/incident.service.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -1
- package/dist/integration-profile/dto/test.dto.d.ts +13 -0
- package/dist/integration-profile/dto/test.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/test.dto.js +26 -0
- package/dist/integration-profile/dto/test.dto.js.map +1 -0
- package/dist/integration-profile/integration-profile.controller.d.ts +12 -1
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.controller.js +9 -1
- package/dist/integration-profile/integration-profile.controller.js.map +1 -1
- package/dist/integration-profile/integration-profile.module.d.ts +6 -1
- package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.module.js +26 -3
- package/dist/integration-profile/integration-profile.module.js.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.js +9 -1
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts +26 -7
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +94 -91
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.d.ts +13 -1
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +20 -5
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/integration-profile/testers/ai.testers.d.ts +3 -0
- package/dist/integration-profile/testers/ai.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/ai.testers.js +125 -0
- package/dist/integration-profile/testers/ai.testers.js.map +1 -0
- package/dist/integration-profile/testers/captcha.testers.d.ts +3 -0
- package/dist/integration-profile/testers/captcha.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/captcha.testers.js +121 -0
- package/dist/integration-profile/testers/captcha.testers.js.map +1 -0
- package/dist/integration-profile/testers/email.testers.d.ts +3 -0
- package/dist/integration-profile/testers/email.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/email.testers.js +128 -0
- package/dist/integration-profile/testers/email.testers.js.map +1 -0
- package/dist/integration-profile/testers/http-probe.d.ts +57 -0
- package/dist/integration-profile/testers/http-probe.d.ts.map +1 -0
- package/dist/integration-profile/testers/http-probe.js +140 -0
- package/dist/integration-profile/testers/http-probe.js.map +1 -0
- package/dist/integration-profile/testers/index.d.ts +12 -0
- package/dist/integration-profile/testers/index.d.ts.map +1 -0
- package/dist/integration-profile/testers/index.js +45 -0
- package/dist/integration-profile/testers/index.js.map +1 -0
- package/dist/integration-profile/testers/infrastructure.testers.d.ts +3 -0
- package/dist/integration-profile/testers/infrastructure.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/infrastructure.testers.js +105 -0
- package/dist/integration-profile/testers/infrastructure.testers.js.map +1 -0
- package/dist/integration-profile/testers/integration-tester.registry.d.ts +17 -0
- package/dist/integration-profile/testers/integration-tester.registry.d.ts.map +1 -0
- package/dist/integration-profile/testers/integration-tester.registry.js +52 -0
- package/dist/integration-profile/testers/integration-tester.registry.js.map +1 -0
- package/dist/integration-profile/testers/integration-tester.types.d.ts +55 -0
- package/dist/integration-profile/testers/integration-tester.types.d.ts.map +1 -0
- package/dist/integration-profile/testers/integration-tester.types.js +16 -0
- package/dist/integration-profile/testers/integration-tester.types.js.map +1 -0
- package/dist/integration-profile/testers/messaging.testers.d.ts +3 -0
- package/dist/integration-profile/testers/messaging.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/messaging.testers.js +70 -0
- package/dist/integration-profile/testers/messaging.testers.js.map +1 -0
- package/dist/integration-profile/testers/oauth.testers.d.ts +3 -0
- package/dist/integration-profile/testers/oauth.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/oauth.testers.js +285 -0
- package/dist/integration-profile/testers/oauth.testers.js.map +1 -0
- package/dist/integration-profile/testers/payment.testers.d.ts +3 -0
- package/dist/integration-profile/testers/payment.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/payment.testers.js +169 -0
- package/dist/integration-profile/testers/payment.testers.js.map +1 -0
- package/dist/integration-profile/testers/storage.testers.d.ts +3 -0
- package/dist/integration-profile/testers/storage.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/storage.testers.js +208 -0
- package/dist/integration-profile/testers/storage.testers.js.map +1 -0
- package/dist/language/en.json +4 -2
- package/dist/language/pt.json +4 -2
- package/dist/leaked-password/leaked-password.controller.d.ts +24 -0
- package/dist/leaked-password/leaked-password.controller.d.ts.map +1 -0
- package/dist/leaked-password/leaked-password.controller.js +78 -0
- package/dist/leaked-password/leaked-password.controller.js.map +1 -0
- package/dist/leaked-password/leaked-password.module.d.ts +8 -0
- package/dist/leaked-password/leaked-password.module.d.ts.map +1 -0
- package/dist/leaked-password/leaked-password.module.js +31 -0
- package/dist/leaked-password/leaked-password.module.js.map +1 -0
- package/dist/leaked-password/leaked-password.service.d.ts +64 -0
- package/dist/leaked-password/leaked-password.service.d.ts.map +1 -0
- package/dist/leaked-password/leaked-password.service.js +218 -0
- package/dist/leaked-password/leaked-password.service.js.map +1 -0
- package/dist/locale/locale.controller.d.ts +1 -1
- package/dist/locale/locale.service.d.ts +1 -1
- package/dist/mail/dto/preview.dto.d.ts +18 -0
- package/dist/mail/dto/preview.dto.d.ts.map +1 -0
- package/dist/mail/dto/preview.dto.js +66 -0
- package/dist/mail/dto/preview.dto.js.map +1 -0
- package/dist/mail/dto/send.dto.d.ts +7 -0
- 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 +4 -0
- package/dist/mail/mail.controller.d.ts.map +1 -1
- package/dist/mail/mail.controller.js +16 -2
- package/dist/mail/mail.controller.js.map +1 -1
- package/dist/mail/mail.service.d.ts +23 -2
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +102 -13
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mail-layout/dto/update.dto.d.ts +17 -0
- package/dist/mail-layout/dto/update.dto.d.ts.map +1 -0
- package/dist/mail-layout/dto/update.dto.js +27 -0
- package/dist/mail-layout/dto/update.dto.js.map +1 -0
- package/dist/mail-layout/mail-layout.controller.d.ts +15 -0
- package/dist/mail-layout/mail-layout.controller.d.ts.map +1 -0
- package/dist/mail-layout/mail-layout.controller.js +71 -0
- package/dist/mail-layout/mail-layout.controller.js.map +1 -0
- package/dist/mail-layout/mail-layout.module.d.ts +3 -0
- package/dist/mail-layout/mail-layout.module.d.ts.map +1 -0
- package/dist/mail-layout/mail-layout.module.js +35 -0
- package/dist/mail-layout/mail-layout.module.js.map +1 -0
- package/dist/mail-layout/mail-layout.service.d.ts +27 -0
- package/dist/mail-layout/mail-layout.service.d.ts.map +1 -0
- package/dist/mail-layout/mail-layout.service.js +80 -0
- package/dist/mail-layout/mail-layout.service.js.map +1 -0
- package/dist/mcp-chat/mcp-chat.controller.d.ts +2 -2
- package/dist/mcp-chat/mcp-chat.service.d.ts +24 -2
- package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +80 -24
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/menu/menu.controller.d.ts +3 -3
- package/dist/menu/menu.service.d.ts +3 -3
- package/dist/notification/notification.controller.d.ts +12 -12
- package/dist/notification/notification.service.d.ts +12 -12
- package/dist/oauth/oauth-token-broker.service.d.ts +72 -0
- package/dist/oauth/oauth-token-broker.service.d.ts.map +1 -0
- package/dist/oauth/oauth-token-broker.service.js +258 -0
- package/dist/oauth/oauth-token-broker.service.js.map +1 -0
- package/dist/oauth/oauth.module.d.ts.map +1 -1
- package/dist/oauth/oauth.module.js +3 -1
- package/dist/oauth/oauth.module.js.map +1 -1
- package/dist/oauth/oauth.service.d.ts +2 -0
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +6 -3
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/pdf/pdf.service.d.ts +8 -0
- package/dist/pdf/pdf.service.d.ts.map +1 -1
- package/dist/pdf/pdf.service.js +52 -0
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/presence/presence.controller.d.ts +16 -0
- package/dist/presence/presence.controller.d.ts.map +1 -0
- package/dist/presence/presence.controller.js +60 -0
- package/dist/presence/presence.controller.js.map +1 -0
- package/dist/presence/presence.listener.d.ts +29 -0
- package/dist/presence/presence.listener.d.ts.map +1 -0
- package/dist/presence/presence.listener.js +82 -0
- package/dist/presence/presence.listener.js.map +1 -0
- package/dist/presence/presence.module.d.ts +3 -0
- package/dist/presence/presence.module.d.ts.map +1 -0
- package/dist/presence/presence.module.js +32 -0
- package/dist/presence/presence.module.js.map +1 -0
- package/dist/presence/presence.service.d.ts +42 -0
- package/dist/presence/presence.service.d.ts.map +1 -0
- package/dist/presence/presence.service.js +147 -0
- package/dist/presence/presence.service.js.map +1 -0
- package/dist/profile/dto/delete-account.dto.d.ts +8 -0
- package/dist/profile/dto/delete-account.dto.d.ts.map +1 -0
- package/dist/profile/dto/delete-account.dto.js +22 -0
- package/dist/profile/dto/delete-account.dto.js.map +1 -0
- package/dist/profile/events/profile-account-deleted.event.d.ts +12 -0
- package/dist/profile/events/profile-account-deleted.event.d.ts.map +1 -0
- package/dist/profile/events/profile-account-deleted.event.js +17 -0
- package/dist/profile/events/profile-account-deleted.event.js.map +1 -0
- package/dist/profile/profile.controller.d.ts +21 -15
- package/dist/profile/profile.controller.d.ts.map +1 -1
- package/dist/profile/profile.controller.js +75 -46
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/profile/profile.service.d.ts +30 -18
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +113 -37
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/realtime/realtime.gateway.d.ts +48 -0
- package/dist/realtime/realtime.gateway.d.ts.map +1 -0
- package/dist/realtime/realtime.gateway.js +156 -0
- package/dist/realtime/realtime.gateway.js.map +1 -0
- package/dist/realtime/realtime.module.d.ts +8 -0
- package/dist/realtime/realtime.module.d.ts.map +1 -0
- package/dist/realtime/realtime.module.js +29 -0
- package/dist/realtime/realtime.module.js.map +1 -0
- package/dist/realtime/realtime.service.d.ts +51 -0
- package/dist/realtime/realtime.service.d.ts.map +1 -0
- package/dist/realtime/realtime.service.js +177 -0
- package/dist/realtime/realtime.service.js.map +1 -0
- package/dist/redis/redis-pubsub.d.ts +27 -0
- package/dist/redis/redis-pubsub.d.ts.map +1 -0
- package/dist/redis/redis-pubsub.js +140 -0
- package/dist/redis/redis-pubsub.js.map +1 -0
- package/dist/role/role.controller.d.ts +3 -3
- package/dist/role/role.service.d.ts +3 -3
- package/dist/setting/dto/create.dto.d.ts.map +1 -1
- package/dist/setting/dto/create.dto.js +9 -2
- package/dist/setting/dto/create.dto.js.map +1 -1
- package/dist/setting/setting.controller.d.ts +5 -5
- package/dist/setting/setting.service.d.ts +8 -5
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +25 -1
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/user/dto/create-with-email-and-password.dto.d.ts +2 -0
- package/dist/user/dto/create-with-email-and-password.dto.d.ts.map +1 -1
- package/dist/user/dto/create-with-email-and-password.dto.js +5 -0
- package/dist/user/dto/create-with-email-and-password.dto.js.map +1 -1
- package/dist/user/dto/query-user-mail-list.dto.d.ts +5 -0
- package/dist/user/dto/query-user-mail-list.dto.d.ts.map +1 -0
- package/dist/user/dto/query-user-mail-list.dto.js +25 -0
- package/dist/user/dto/query-user-mail-list.dto.js.map +1 -0
- package/dist/user/user-credential.util.d.ts +19 -0
- package/dist/user/user-credential.util.d.ts.map +1 -0
- package/dist/user/user-credential.util.js +32 -0
- package/dist/user/user-credential.util.js.map +1 -0
- package/dist/user/user.controller.d.ts +17 -5
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +18 -3
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +28 -6
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +204 -13
- package/dist/user/user.service.js.map +1 -1
- package/dist/validators/is-strong-password-with-settings.validator.d.ts.map +1 -1
- package/dist/validators/is-strong-password-with-settings.validator.js +11 -0
- package/dist/validators/is-strong-password-with-settings.validator.js.map +1 -1
- package/dist/validators/service-locator.d.ts +9 -0
- package/dist/validators/service-locator.d.ts.map +1 -1
- package/dist/validators/service-locator.js +12 -0
- package/dist/validators/service-locator.js.map +1 -1
- package/dist/whatsapp-sender/whatsapp-sender.module.d.ts +3 -0
- package/dist/whatsapp-sender/whatsapp-sender.module.d.ts.map +1 -0
- package/dist/whatsapp-sender/whatsapp-sender.module.js +23 -0
- package/dist/whatsapp-sender/whatsapp-sender.module.js.map +1 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +50 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.js +224 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -0
- package/hedhog/data/integration_provider.yaml +16 -0
- package/hedhog/data/mail.yaml +88 -61
- package/hedhog/data/mail_layout.yaml +188 -0
- package/hedhog/data/menu.yaml +67 -3
- package/hedhog/data/route.yaml +135 -0
- package/hedhog/data/setting_group.yaml +139 -1
- package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +36 -0
- package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +2 -0
- package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +10 -7
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +7 -4
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-form.tsx.ejs +580 -0
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-preview.tsx.ejs +150 -0
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-versions.tsx.ejs +199 -0
- package/hedhog/frontend/app/ai_instruction/_components/prompt-diff.tsx.ejs +63 -0
- package/hedhog/frontend/app/ai_instruction/_lib/line-diff.spec.ts.ejs +59 -0
- package/hedhog/frontend/app/ai_instruction/_lib/line-diff.ts.ejs +72 -0
- package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.spec.ts.ejs +170 -0
- package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.ts.ejs +167 -0
- package/hedhog/frontend/app/ai_instruction/_lib/types.ts.ejs +99 -0
- package/hedhog/frontend/app/ai_instruction/page.tsx.ejs +422 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +51 -4
- package/hedhog/frontend/app/incidents/page.tsx.ejs +400 -0
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +153 -84
- package/hedhog/frontend/app/mail/layout/page.tsx.ejs +344 -0
- package/hedhog/frontend/app/mail/template/page.tsx.ejs +155 -121
- package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +129 -17
- package/hedhog/frontend/app/pages/_components/page-delete-dialog.tsx.ejs +343 -0
- package/hedhog/frontend/app/pages/_components/page-detail-sheet.tsx.ejs +438 -0
- package/hedhog/frontend/app/pages/_components/page-wizard-sheet.tsx.ejs +1009 -0
- package/hedhog/frontend/app/pages/_components/system-page-types.ts.ejs +56 -0
- package/hedhog/frontend/app/pages/_components/use-system-pages.ts.ejs +171 -0
- package/hedhog/frontend/app/pages/page.tsx.ejs +514 -0
- package/hedhog/frontend/app/users/page.tsx.ejs +119 -13
- package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +231 -0
- package/hedhog/frontend/app/users/user-mails.tsx.ejs +399 -0
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +60 -75
- package/hedhog/frontend/messages/en.json +499 -3
- package/hedhog/frontend/messages/pt.json +499 -3
- package/hedhog/table/ai_agent.yaml +1 -0
- package/hedhog/table/ai_instruction.yaml +15 -0
- package/hedhog/table/leaked_password.yaml +18 -0
- package/hedhog/table/mail_layout.yaml +35 -0
- package/hedhog/table/mail_var.yaml +4 -0
- package/hedhog/table/setting.yaml +1 -0
- package/hedhog/table/system_incident.yaml +59 -0
- package/hedhog/table/user_presence.yaml +36 -0
- package/package.json +8 -4
- package/src/access-log/access-log.interceptor.ts +4 -2
- package/src/ai/ai-attachment.fragments.spec.ts +319 -0
- package/src/ai/ai-attachment.fragments.ts +35 -0
- package/src/ai/ai.prompts.ts +38 -0
- package/src/ai/ai.service.spec.ts +280 -0
- package/src/ai/ai.service.ts +338 -82
- package/src/ai/dto/chat.dto.ts +33 -3
- package/src/ai/dto/create-agent.dto.ts +2 -2
- package/src/ai/dto/update-agent.dto.ts +2 -2
- package/src/ai-instruction/ai-instruction.controller.ts +40 -9
- package/src/ai-instruction/ai-instruction.module.ts +13 -2
- package/src/ai-instruction/ai-instruction.service.spec.ts +658 -0
- package/src/ai-instruction/ai-instruction.service.ts +661 -62
- package/src/ai-instruction/ai-prompt-builder.service.ts +10 -1
- package/src/ai-instruction/ai-prompt-registry.ts +87 -0
- package/src/ai-instruction/ai-prompt-resolver.service.spec.ts +375 -0
- package/src/ai-instruction/ai-prompt-resolver.service.ts +295 -0
- package/src/ai-instruction/ai-prompt-template.util.spec.ts +162 -0
- package/src/ai-instruction/ai-prompt-template.util.ts +199 -0
- package/src/ai-instruction/dto/create-ai-instruction.dto.ts +39 -3
- package/src/ai-instruction/dto/preview-ai-instruction.dto.ts +26 -0
- package/src/ai-instruction/dto/update-ai-instruction.dto.ts +42 -1
- package/src/ai-instruction/types/ai-instruction.types.ts +24 -1
- package/src/auth/auth.controller.ts +31 -5
- package/src/auth/auth.service.spec.ts +3 -1
- package/src/auth/auth.service.ts +101 -36
- package/src/captcha/captcha.module.ts +10 -0
- package/src/captcha/captcha.service.spec.ts +210 -0
- package/src/captcha/captcha.service.ts +191 -0
- package/src/challenge/challenge.service.spec.ts +39 -4
- package/src/challenge/challenge.service.ts +72 -17
- package/src/core.module.ts +27 -1
- package/src/file/file.service.spec.ts +105 -0
- package/src/file/file.service.ts +60 -3
- package/src/incident/incident-notifier.service.spec.ts +205 -0
- package/src/incident/incident-notifier.service.ts +241 -0
- package/src/incident/incident.controller.ts +38 -0
- package/src/incident/incident.module.ts +19 -0
- package/src/incident/incident.service.spec.ts +150 -0
- package/src/incident/incident.service.ts +209 -0
- package/src/index.ts +37 -0
- package/src/integration-profile/dto/test.dto.ts +19 -0
- package/src/integration-profile/integration-profile.controller.ts +10 -1
- package/src/integration-profile/integration-profile.module.spec.ts +51 -0
- package/src/integration-profile/integration-profile.module.ts +23 -4
- package/src/integration-profile/integration-profile.secrets.ts +9 -1
- package/src/integration-profile/integration-profile.service.spec.ts +279 -0
- package/src/integration-profile/integration-profile.service.ts +120 -124
- package/src/integration-profile/integration-profile.utils.spec.ts +22 -0
- package/src/integration-profile/integration-profile.utils.ts +40 -3
- package/src/integration-profile/testers/ai.testers.ts +165 -0
- package/src/integration-profile/testers/captcha.testers.spec.ts +92 -0
- package/src/integration-profile/testers/captcha.testers.ts +139 -0
- package/src/integration-profile/testers/email.testers.ts +161 -0
- package/src/integration-profile/testers/http-probe.spec.ts +153 -0
- package/src/integration-profile/testers/http-probe.ts +171 -0
- package/src/integration-profile/testers/index.ts +30 -0
- package/src/integration-profile/testers/infrastructure.testers.ts +122 -0
- package/src/integration-profile/testers/integration-tester.registry.spec.ts +75 -0
- package/src/integration-profile/testers/integration-tester.registry.ts +43 -0
- package/src/integration-profile/testers/integration-tester.types.ts +61 -0
- package/src/integration-profile/testers/messaging.testers.ts +102 -0
- package/src/integration-profile/testers/oauth.testers.spec.ts +220 -0
- package/src/integration-profile/testers/oauth.testers.ts +305 -0
- package/src/integration-profile/testers/payment.testers.ts +185 -0
- package/src/integration-profile/testers/remaining.testers.spec.ts +401 -0
- package/src/integration-profile/testers/storage.testers.spec.ts +185 -0
- package/src/integration-profile/testers/storage.testers.ts +226 -0
- package/src/integration-profile/testers/tester-coverage.spec.ts +58 -0
- package/src/language/en.json +4 -2
- package/src/language/pt.json +4 -2
- package/src/leaked-password/README.md +83 -0
- package/src/leaked-password/leaked-password.controller.ts +52 -0
- package/src/leaked-password/leaked-password.module.ts +18 -0
- package/src/leaked-password/leaked-password.service.ts +242 -0
- package/src/leaked-password/seed/build-seed-sql.mjs +180 -0
- package/src/leaked-password/seed/common-passwords.txt +335 -0
- package/src/mail/dto/preview.dto.ts +50 -0
- package/src/mail/dto/send.dto.ts +14 -1
- package/src/mail/mail-template.spec.ts +197 -0
- package/src/mail/mail.controller.ts +9 -2
- package/src/mail/mail.service.spec.ts +116 -1
- package/src/mail/mail.service.ts +118 -12
- package/src/mail-layout/dto/update.dto.ts +19 -0
- package/src/mail-layout/mail-layout.controller.ts +47 -0
- package/src/mail-layout/mail-layout.module.ts +22 -0
- package/src/mail-layout/mail-layout.service.ts +94 -0
- package/src/mcp-chat/mcp-chat.service.spec.ts +236 -0
- package/src/mcp-chat/mcp-chat.service.ts +122 -26
- package/src/oauth/oauth-token-broker.service.spec.ts +172 -0
- package/src/oauth/oauth-token-broker.service.ts +363 -0
- package/src/oauth/oauth.module.ts +3 -1
- package/src/oauth/oauth.service.ts +8 -2
- package/src/pdf/pdf.service.ts +75 -0
- package/src/presence/presence.controller.ts +29 -0
- package/src/presence/presence.listener.ts +53 -0
- package/src/presence/presence.module.ts +19 -0
- package/src/presence/presence.service.spec.ts +97 -0
- package/src/presence/presence.service.ts +160 -0
- package/src/profile/dto/delete-account.dto.ts +11 -0
- package/src/profile/events/profile-account-deleted.event.ts +13 -0
- package/src/profile/profile.controller.ts +46 -34
- package/src/profile/profile.service.ts +138 -38
- package/src/realtime/realtime.gateway.ts +138 -0
- package/src/realtime/realtime.module.ts +16 -0
- package/src/realtime/realtime.service.spec.ts +50 -0
- package/src/realtime/realtime.service.ts +187 -0
- package/src/redis/redis-pubsub.ts +179 -0
- package/src/setting/dto/create.dto.ts +13 -2
- package/src/setting/setting.service.spec.ts +56 -0
- package/src/setting/setting.service.ts +29 -1
- package/src/user/dto/create-with-email-and-password.dto.ts +6 -1
- package/src/user/dto/query-user-mail-list.dto.ts +10 -0
- package/src/user/user-credential.util.ts +35 -0
- package/src/user/user.controller.ts +17 -2
- package/src/user/user.service.spec.ts +153 -0
- package/src/user/user.service.ts +255 -12
- package/src/validators/is-strong-password-with-settings.validator.ts +12 -0
- package/src/validators/service-locator.ts +15 -0
- package/src/whatsapp-sender/whatsapp-sender.module.ts +10 -0
- package/src/whatsapp-sender/whatsapp-sender.service.ts +313 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { BadRequestException } from '@nestjs/common';
|
|
2
|
+
import { createHash, createHmac } from 'crypto';
|
|
3
|
+
import { CaptchaService } from './captcha.service';
|
|
4
|
+
|
|
5
|
+
describe('CaptchaService', () => {
|
|
6
|
+
let prisma: any;
|
|
7
|
+
let service: CaptchaService;
|
|
8
|
+
const originalFetch = global.fetch;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
prisma = { integration_profile: { findFirst: jest.fn() } };
|
|
12
|
+
service = new CaptchaService(prisma);
|
|
13
|
+
global.fetch = jest.fn() as never;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
global.fetch = originalFetch;
|
|
18
|
+
jest.restoreAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function mockFetch(success: boolean) {
|
|
22
|
+
(global.fetch as jest.Mock).mockResolvedValue({
|
|
23
|
+
json: async () => ({ success }),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('verify', () => {
|
|
28
|
+
it('rejects an empty token before calling the provider', async () => {
|
|
29
|
+
await expect(service.verify('', 'recaptcha', {})).rejects.toBeInstanceOf(
|
|
30
|
+
BadRequestException,
|
|
31
|
+
);
|
|
32
|
+
expect(global.fetch).not.toHaveBeenCalled();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('rejects an unsupported provider', async () => {
|
|
36
|
+
await expect(
|
|
37
|
+
service.verify('token', 'hcaptcha', {}),
|
|
38
|
+
).rejects.toThrow('Unsupported CAPTCHA provider: hcaptcha.');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('recaptcha', () => {
|
|
42
|
+
it('accepts a token the provider confirms', async () => {
|
|
43
|
+
mockFetch(true);
|
|
44
|
+
await expect(
|
|
45
|
+
service.verify('token', 'recaptcha', { secret_key: 'secret' }),
|
|
46
|
+
).resolves.toBeUndefined();
|
|
47
|
+
|
|
48
|
+
const [url, init] = (global.fetch as jest.Mock).mock.calls[0];
|
|
49
|
+
expect(url).toBe('https://www.google.com/recaptcha/api/siteverify');
|
|
50
|
+
expect(init.method).toBe('POST');
|
|
51
|
+
expect(String(init.body)).toContain('secret=secret');
|
|
52
|
+
expect(String(init.body)).toContain('response=token');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('rejects a token the provider refuses', async () => {
|
|
56
|
+
mockFetch(false);
|
|
57
|
+
await expect(
|
|
58
|
+
service.verify('token', 'recaptcha', { secret_key: 'secret' }),
|
|
59
|
+
).rejects.toThrow('CAPTCHA validation failed.');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('cloudflare-turnstile', () => {
|
|
64
|
+
it('calls the turnstile endpoint', async () => {
|
|
65
|
+
mockFetch(true);
|
|
66
|
+
await service.verify('token', 'cloudflare-turnstile', {
|
|
67
|
+
secret_key: 'secret',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect((global.fetch as jest.Mock).mock.calls[0][0]).toBe(
|
|
71
|
+
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('rejects a refused token', async () => {
|
|
76
|
+
mockFetch(false);
|
|
77
|
+
await expect(
|
|
78
|
+
service.verify('token', 'cloudflare-turnstile', { secret_key: 's' }),
|
|
79
|
+
).rejects.toThrow('CAPTCHA validation failed.');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('altcha', () => {
|
|
84
|
+
const hmacKey = 'hmac-key';
|
|
85
|
+
|
|
86
|
+
function buildToken(overrides: Partial<Record<string, unknown>> = {}) {
|
|
87
|
+
const salt = 'salt';
|
|
88
|
+
const number = 12345;
|
|
89
|
+
const challenge = createHash('sha256')
|
|
90
|
+
.update(salt + String(number))
|
|
91
|
+
.digest('hex');
|
|
92
|
+
const signature = createHmac('sha256', hmacKey)
|
|
93
|
+
.update(challenge)
|
|
94
|
+
.digest('hex');
|
|
95
|
+
|
|
96
|
+
const payload = {
|
|
97
|
+
algorithm: 'SHA-256',
|
|
98
|
+
challenge,
|
|
99
|
+
number,
|
|
100
|
+
salt,
|
|
101
|
+
signature,
|
|
102
|
+
...overrides,
|
|
103
|
+
};
|
|
104
|
+
return Buffer.from(JSON.stringify(payload)).toString('base64');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
it('accepts a correctly solved challenge', async () => {
|
|
108
|
+
await expect(
|
|
109
|
+
service.verify(buildToken(), 'altcha', { hmac_key: hmacKey }),
|
|
110
|
+
).resolves.toBeUndefined();
|
|
111
|
+
// Altcha e verificado localmente: nenhuma chamada de rede.
|
|
112
|
+
expect(global.fetch).not.toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('rejects a malformed token', async () => {
|
|
116
|
+
await expect(
|
|
117
|
+
service.verify('not-base64-json', 'altcha', { hmac_key: hmacKey }),
|
|
118
|
+
).rejects.toThrow('Invalid CAPTCHA token format.');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('rejects when the proof of work does not match the challenge', async () => {
|
|
122
|
+
await expect(
|
|
123
|
+
service.verify(buildToken({ number: 999 }), 'altcha', {
|
|
124
|
+
hmac_key: hmacKey,
|
|
125
|
+
}),
|
|
126
|
+
).rejects.toThrow('CAPTCHA validation failed.');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('rejects a challenge we did not sign', async () => {
|
|
130
|
+
await expect(
|
|
131
|
+
service.verify(buildToken(), 'altcha', { hmac_key: 'other-key' }),
|
|
132
|
+
).rejects.toThrow('CAPTCHA validation failed.');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('verifyForProfile', () => {
|
|
138
|
+
it('resolves the provider from the integration profile', async () => {
|
|
139
|
+
prisma.integration_profile.findFirst.mockResolvedValue({
|
|
140
|
+
config: { secret_key: 'secret' },
|
|
141
|
+
integration_provider: { slug: 'recaptcha' },
|
|
142
|
+
});
|
|
143
|
+
mockFetch(true);
|
|
144
|
+
|
|
145
|
+
await expect(service.verifyForProfile(7, 'token')).resolves.toBeUndefined();
|
|
146
|
+
expect(prisma.integration_profile.findFirst).toHaveBeenCalledWith(
|
|
147
|
+
expect.objectContaining({ where: { id: 7 } }),
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('refuses when no profile id is configured', async () => {
|
|
152
|
+
await expect(service.verifyForProfile(null, 'token')).rejects.toThrow(
|
|
153
|
+
'CAPTCHA is enabled but no integration profile is configured.',
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('refuses when the profile does not exist', async () => {
|
|
158
|
+
prisma.integration_profile.findFirst.mockResolvedValue(null);
|
|
159
|
+
await expect(service.verifyForProfile(7, 'token')).rejects.toThrow(
|
|
160
|
+
'CAPTCHA integration profile not found.',
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('getPublicConfig', () => {
|
|
166
|
+
it('returns the provider and site key', async () => {
|
|
167
|
+
prisma.integration_profile.findFirst.mockResolvedValue({
|
|
168
|
+
config: { site_key: 'public-key', secret_key: 'secret' },
|
|
169
|
+
integration_provider: { slug: 'cloudflare-turnstile' },
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const config = await service.getPublicConfig(7);
|
|
173
|
+
|
|
174
|
+
expect(config).toEqual({
|
|
175
|
+
enabled: true,
|
|
176
|
+
provider_slug: 'cloudflare-turnstile',
|
|
177
|
+
site_key: 'public-key',
|
|
178
|
+
});
|
|
179
|
+
// O segredo nunca sai do servidor.
|
|
180
|
+
expect(JSON.stringify(config)).not.toContain('secret');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('reports disabled when there is no profile', async () => {
|
|
184
|
+
await expect(service.getPublicConfig(null)).resolves.toEqual({
|
|
185
|
+
enabled: false,
|
|
186
|
+
provider_slug: null,
|
|
187
|
+
site_key: null,
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('reports disabled when the caller says the feature is off', async () => {
|
|
192
|
+
await expect(service.getPublicConfig(7, false)).resolves.toEqual({
|
|
193
|
+
enabled: false,
|
|
194
|
+
provider_slug: null,
|
|
195
|
+
site_key: null,
|
|
196
|
+
});
|
|
197
|
+
expect(prisma.integration_profile.findFirst).not.toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('extractSiteKey', () => {
|
|
202
|
+
it('accepts the naming variants providers use', () => {
|
|
203
|
+
expect(CaptchaService.extractSiteKey({ site_key: 'a' })).toBe('a');
|
|
204
|
+
expect(CaptchaService.extractSiteKey({ sitekey: 'b' })).toBe('b');
|
|
205
|
+
expect(CaptchaService.extractSiteKey({ public_key: 'c' })).toBe('c');
|
|
206
|
+
expect(CaptchaService.extractSiteKey({ secret_key: 'x' })).toBeNull();
|
|
207
|
+
expect(CaptchaService.extractSiteKey(null)).toBeNull();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { PrismaService } from '@hed-hog/api-prisma';
|
|
2
|
+
import {
|
|
3
|
+
BadRequestException,
|
|
4
|
+
Inject,
|
|
5
|
+
Injectable,
|
|
6
|
+
forwardRef,
|
|
7
|
+
} from '@nestjs/common';
|
|
8
|
+
import { createHash, createHmac } from 'crypto';
|
|
9
|
+
|
|
10
|
+
export const CAPTCHA_PROVIDERS = [
|
|
11
|
+
'recaptcha',
|
|
12
|
+
'cloudflare-turnstile',
|
|
13
|
+
'altcha',
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
export type CaptchaProviderSlug = (typeof CAPTCHA_PROVIDERS)[number];
|
|
17
|
+
|
|
18
|
+
export interface CaptchaPublicConfig {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
provider_slug: string | null;
|
|
21
|
+
site_key: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface AltchaPayload {
|
|
25
|
+
algorithm: string;
|
|
26
|
+
challenge: string;
|
|
27
|
+
number: number;
|
|
28
|
+
salt: string;
|
|
29
|
+
signature: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Server-side CAPTCHA verification shared by every public form in the platform.
|
|
34
|
+
*
|
|
35
|
+
* Credentials live in an `integration_profile`, so a form only stores the profile
|
|
36
|
+
* id and never the secret itself.
|
|
37
|
+
*/
|
|
38
|
+
@Injectable()
|
|
39
|
+
export class CaptchaService {
|
|
40
|
+
constructor(
|
|
41
|
+
@Inject(forwardRef(() => PrismaService))
|
|
42
|
+
private readonly prisma: PrismaService,
|
|
43
|
+
) {}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Verifies a token against the provider configured in an integration profile.
|
|
47
|
+
* Throws `BadRequestException` when the challenge does not check out.
|
|
48
|
+
*/
|
|
49
|
+
async verifyForProfile(
|
|
50
|
+
integrationProfileId: number | null | undefined,
|
|
51
|
+
token: string | null | undefined,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
if (!integrationProfileId) {
|
|
54
|
+
throw new BadRequestException(
|
|
55
|
+
'CAPTCHA is enabled but no integration profile is configured.',
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const profile = await (this.prisma as any).integration_profile.findFirst({
|
|
60
|
+
where: { id: integrationProfileId },
|
|
61
|
+
select: {
|
|
62
|
+
config: true,
|
|
63
|
+
integration_provider: { select: { slug: true } },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!profile) {
|
|
68
|
+
throw new BadRequestException('CAPTCHA integration profile not found.');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await this.verify(
|
|
72
|
+
token ?? '',
|
|
73
|
+
profile.integration_provider?.slug as string,
|
|
74
|
+
(profile.config ?? {}) as Record<string, string>,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Verifies a token against an explicit provider and its credentials. */
|
|
79
|
+
async verify(
|
|
80
|
+
token: string,
|
|
81
|
+
providerSlug: string,
|
|
82
|
+
config: Record<string, string>,
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
if (!token) {
|
|
85
|
+
throw new BadRequestException('CAPTCHA token is required.');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
switch (providerSlug) {
|
|
89
|
+
case 'recaptcha':
|
|
90
|
+
await this.verifyRemote(
|
|
91
|
+
'https://www.google.com/recaptcha/api/siteverify',
|
|
92
|
+
config.secret_key,
|
|
93
|
+
token,
|
|
94
|
+
);
|
|
95
|
+
break;
|
|
96
|
+
|
|
97
|
+
case 'cloudflare-turnstile':
|
|
98
|
+
await this.verifyRemote(
|
|
99
|
+
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
|
|
100
|
+
config.secret_key,
|
|
101
|
+
token,
|
|
102
|
+
);
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case 'altcha':
|
|
106
|
+
this.verifyAltcha(token, config);
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
default:
|
|
110
|
+
throw new BadRequestException(
|
|
111
|
+
`Unsupported CAPTCHA provider: ${providerSlug}.`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Public settings a front end needs to mount the widget. Only the site key is
|
|
118
|
+
* returned - the secret never leaves the server.
|
|
119
|
+
*/
|
|
120
|
+
async getPublicConfig(
|
|
121
|
+
integrationProfileId: number | null | undefined,
|
|
122
|
+
enabled = true,
|
|
123
|
+
): Promise<CaptchaPublicConfig> {
|
|
124
|
+
if (!enabled || !integrationProfileId) {
|
|
125
|
+
return { enabled: false, provider_slug: null, site_key: null };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const profile = await (this.prisma as any).integration_profile.findFirst({
|
|
129
|
+
where: { id: integrationProfileId },
|
|
130
|
+
select: {
|
|
131
|
+
config: true,
|
|
132
|
+
integration_provider: { select: { slug: true } },
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
enabled: true,
|
|
138
|
+
provider_slug: profile?.integration_provider?.slug ?? null,
|
|
139
|
+
site_key: CaptchaService.extractSiteKey(profile?.config ?? null),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static extractSiteKey(
|
|
144
|
+
config: Record<string, unknown> | null,
|
|
145
|
+
): string | null {
|
|
146
|
+
if (!config) return null;
|
|
147
|
+
const value = config.site_key ?? config.sitekey ?? config.public_key;
|
|
148
|
+
return value ? String(value) : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private async verifyRemote(
|
|
152
|
+
url: string,
|
|
153
|
+
secret: string,
|
|
154
|
+
token: string,
|
|
155
|
+
): Promise<void> {
|
|
156
|
+
const body = new URLSearchParams({ secret, response: token });
|
|
157
|
+
const response = await fetch(url, { method: 'POST', body });
|
|
158
|
+
const json = (await response.json()) as { success: boolean };
|
|
159
|
+
|
|
160
|
+
if (!json.success) {
|
|
161
|
+
throw new BadRequestException('CAPTCHA validation failed.');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Altcha is verified locally: the client solves a proof-of-work, and the HMAC
|
|
167
|
+
* proves the challenge was issued by us.
|
|
168
|
+
*/
|
|
169
|
+
private verifyAltcha(token: string, config: Record<string, string>): void {
|
|
170
|
+
let payload: AltchaPayload;
|
|
171
|
+
try {
|
|
172
|
+
payload = JSON.parse(Buffer.from(token, 'base64').toString('utf8'));
|
|
173
|
+
} catch {
|
|
174
|
+
throw new BadRequestException('Invalid CAPTCHA token format.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const hash = createHash('sha256')
|
|
178
|
+
.update(payload.salt + String(payload.number))
|
|
179
|
+
.digest('hex');
|
|
180
|
+
if (hash !== payload.challenge) {
|
|
181
|
+
throw new BadRequestException('CAPTCHA validation failed.');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const hmac = createHmac('sha256', config.hmac_key)
|
|
185
|
+
.update(payload.challenge)
|
|
186
|
+
.digest('hex');
|
|
187
|
+
if (hmac !== payload.signature) {
|
|
188
|
+
throw new BadRequestException('CAPTCHA validation failed.');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -129,12 +129,15 @@ describe('ChallengeService', () => {
|
|
|
129
129
|
).rejects.toThrow(BadRequestException);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
it('cria challenge e envia email com link
|
|
132
|
+
it('cria challenge e envia email com link contendo o token original (sem recodificação)', async () => {
|
|
133
133
|
const { service, deps } = makeService();
|
|
134
134
|
deps.prisma.user_identifier_challenge.count.mockResolvedValue(0);
|
|
135
135
|
deps.prisma.user_identifier.findFirst.mockResolvedValue({
|
|
136
136
|
id: 10,
|
|
137
|
-
user: {
|
|
137
|
+
user: {
|
|
138
|
+
name: 'Bob',
|
|
139
|
+
user_credential: [{ type: 'password', revoked_at: null }],
|
|
140
|
+
},
|
|
138
141
|
});
|
|
139
142
|
deps.setting.getSettingValues.mockResolvedValue({
|
|
140
143
|
'mfa-challenge-expiration-minutes': 15,
|
|
@@ -146,19 +149,51 @@ describe('ChallengeService', () => {
|
|
|
146
149
|
const result = await service.forgotEmail('en', 'a@b.com');
|
|
147
150
|
|
|
148
151
|
expect(result).toEqual({ id: 1 });
|
|
149
|
-
const base64 = Buffer.from('tok').toString('base64');
|
|
150
152
|
expect(deps.mail.sendTemplatedMail).toHaveBeenCalledWith(
|
|
151
153
|
'en',
|
|
152
154
|
expect.objectContaining({
|
|
153
155
|
email: 'a@b.com',
|
|
154
156
|
slug: 'auth-forget-password',
|
|
155
157
|
variables: expect.objectContaining({
|
|
156
|
-
link: `https://app.com/reset-password?token=${
|
|
158
|
+
link: `https://app.com/reset-password?token=${encodeURIComponent('tok')}`,
|
|
157
159
|
name: 'Bob',
|
|
158
160
|
}),
|
|
159
161
|
}),
|
|
160
162
|
);
|
|
161
163
|
});
|
|
164
|
+
|
|
165
|
+
it('lança BadRequest quando usuário não possui senha cadastrada (ex: login via SSO)', async () => {
|
|
166
|
+
const { service, deps } = makeService();
|
|
167
|
+
deps.prisma.user_identifier_challenge.count.mockResolvedValue(0);
|
|
168
|
+
deps.prisma.user_identifier.findFirst.mockResolvedValue({
|
|
169
|
+
id: 10,
|
|
170
|
+
user: { name: 'Bob', user_credential: [] },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await expect(
|
|
174
|
+
service.forgotEmail('en', 'a@b.com'),
|
|
175
|
+
).rejects.toThrow(BadRequestException);
|
|
176
|
+
expect(deps.prisma.user_identifier_challenge.create).not.toHaveBeenCalled();
|
|
177
|
+
expect(deps.mail.sendTemplatedMail).not.toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('lança BadRequest quando a única credencial de senha está revogada', async () => {
|
|
181
|
+
const { service, deps } = makeService();
|
|
182
|
+
deps.prisma.user_identifier_challenge.count.mockResolvedValue(0);
|
|
183
|
+
deps.prisma.user_identifier.findFirst.mockResolvedValue({
|
|
184
|
+
id: 10,
|
|
185
|
+
user: {
|
|
186
|
+
name: 'Bob',
|
|
187
|
+
user_credential: [
|
|
188
|
+
{ type: 'password', revoked_at: new Date('2026-01-01T00:00:00Z') },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await expect(
|
|
194
|
+
service.forgotEmail('en', 'a@b.com'),
|
|
195
|
+
).rejects.toThrow(BadRequestException);
|
|
196
|
+
});
|
|
162
197
|
});
|
|
163
198
|
|
|
164
199
|
describe('verifyCodePassword', () => {
|
|
@@ -5,6 +5,7 @@ import { MailService } from "../mail/mail.service";
|
|
|
5
5
|
import { SecurityService } from "../security/security.service";
|
|
6
6
|
import { SettingService } from "../setting/setting.service";
|
|
7
7
|
import { TokenService } from "../token/token.service";
|
|
8
|
+
import { hasPasswordCredential } from "../user/user-credential.util";
|
|
8
9
|
|
|
9
10
|
@Injectable()
|
|
10
11
|
export class ChallengeService {
|
|
@@ -21,7 +22,50 @@ export class ChallengeService {
|
|
|
21
22
|
private readonly mail: MailService,
|
|
22
23
|
) { }
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a chave do app (ex.: "class") a partir do header Origin da
|
|
27
|
+
* requisicao, cruzando contra a setting `app-urls` (o mesmo mapeamento ja
|
|
28
|
+
* usado para montar links de e-mail por app). Usada para decidir a marca
|
|
29
|
+
* (`app-names`) em fluxos onde o frontend nao manda `app` explicitamente no
|
|
30
|
+
* corpo — o Origin ja chega sozinho em toda chamada cross-origin do browser.
|
|
31
|
+
*/
|
|
32
|
+
async resolveAppKeyFromOrigin(origin?: string): Promise<string | undefined> {
|
|
33
|
+
if (!origin) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let originHost: string;
|
|
38
|
+
try {
|
|
39
|
+
originHost = new URL(origin).hostname;
|
|
40
|
+
} catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const settings = await this.setting.getSettingValues(['app-urls']);
|
|
45
|
+
const appUrls: string[] = Array.isArray(settings['app-urls']) ? settings['app-urls'] : [];
|
|
46
|
+
|
|
47
|
+
for (const entry of appUrls) {
|
|
48
|
+
const separatorIndex = entry.indexOf('=');
|
|
49
|
+
if (separatorIndex === -1) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const key = entry.slice(0, separatorIndex).trim();
|
|
54
|
+
const url = entry.slice(separatorIndex + 1).trim();
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
if (new URL(url).hostname === originHost) {
|
|
58
|
+
return key;
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async verifyMfaEmail(locale: string, userId: number, email: string, origin?: string) {
|
|
25
69
|
const settings = await this.setting.getSettingValues([
|
|
26
70
|
'mfa-email-code-length',
|
|
27
71
|
'mfa-challenge-expiration-minutes'
|
|
@@ -75,7 +119,8 @@ export class ChallengeService {
|
|
|
75
119
|
variables: {
|
|
76
120
|
code,
|
|
77
121
|
name: user.name,
|
|
78
|
-
}
|
|
122
|
+
},
|
|
123
|
+
app: await this.resolveAppKeyFromOrigin(origin),
|
|
79
124
|
}
|
|
80
125
|
);
|
|
81
126
|
|
|
@@ -84,7 +129,7 @@ export class ChallengeService {
|
|
|
84
129
|
return { challengeId };
|
|
85
130
|
}
|
|
86
131
|
|
|
87
|
-
async verifyEmail(locale: string, userId: number, email: string) {
|
|
132
|
+
async verifyEmail(locale: string, userId: number, email: string, app?: string, origin?: string) {
|
|
88
133
|
|
|
89
134
|
const identifier = await this.prisma.user_identifier.findFirst({
|
|
90
135
|
where: {
|
|
@@ -139,7 +184,8 @@ export class ChallengeService {
|
|
|
139
184
|
variables: {
|
|
140
185
|
code,
|
|
141
186
|
name: user.name,
|
|
142
|
-
}
|
|
187
|
+
},
|
|
188
|
+
app: app || await this.resolveAppKeyFromOrigin(origin),
|
|
143
189
|
}
|
|
144
190
|
);
|
|
145
191
|
|
|
@@ -202,11 +248,15 @@ export class ChallengeService {
|
|
|
202
248
|
|
|
203
249
|
const userIdentifier = await this.prisma.user_identifier.findFirst({
|
|
204
250
|
where: { type: 'email', value: email, enabled: true },
|
|
205
|
-
include: { user: true },
|
|
251
|
+
include: { user: { include: { user_credential: true } } },
|
|
206
252
|
});
|
|
207
253
|
|
|
208
254
|
if (!userIdentifier) throw new BadRequestException(getLocaleText('forgot.userNotFound', locale, 'User not found'));
|
|
209
255
|
|
|
256
|
+
if (!hasPasswordCredential(userIdentifier.user)) {
|
|
257
|
+
throw new BadRequestException(getLocaleText('forgot.noPassword', locale, 'This account does not have a password set. Sign in using your organization\'s login method instead.'));
|
|
258
|
+
}
|
|
259
|
+
|
|
210
260
|
const settings = await this.setting.getSettingValues([
|
|
211
261
|
'mfa-challenge-expiration-minutes',
|
|
212
262
|
'url',
|
|
@@ -223,16 +273,16 @@ export class ChallengeService {
|
|
|
223
273
|
},
|
|
224
274
|
});
|
|
225
275
|
|
|
226
|
-
const tokenBase64 = Buffer.from(token).toString('base64');
|
|
227
276
|
this.mail.sendTemplatedMail(
|
|
228
277
|
locale,
|
|
229
278
|
{
|
|
230
279
|
email,
|
|
231
280
|
slug: 'auth-forget-password',
|
|
232
281
|
variables: {
|
|
233
|
-
link: `${this.resolveAppUrl(settings, app)}/reset-password?token=${
|
|
282
|
+
link: `${this.resolveAppUrl(settings, app)}/reset-password?token=${encodeURIComponent(token)}`,
|
|
234
283
|
name: userIdentifier.user.name,
|
|
235
|
-
}
|
|
284
|
+
},
|
|
285
|
+
app,
|
|
236
286
|
}
|
|
237
287
|
);
|
|
238
288
|
|
|
@@ -241,9 +291,10 @@ export class ChallengeService {
|
|
|
241
291
|
|
|
242
292
|
/**
|
|
243
293
|
* Cria um challenge de convite de criação de conta vinculado a um
|
|
244
|
-
* `user_identifier` de email já existente e retorna o token
|
|
245
|
-
*
|
|
246
|
-
*
|
|
294
|
+
* `user_identifier` de email já existente e retorna o token opaco (mesmo
|
|
295
|
+
* formato consumido pelo fluxo de reset de senha) — quem monta o link a
|
|
296
|
+
* partir dele deve aplicar `encodeURIComponent`. A validade usa a setting
|
|
297
|
+
* `account-invite-expiration-minutes` (padrão 7 dias).
|
|
247
298
|
*/
|
|
248
299
|
async createAccountInviteChallenge(userIdentifierId: number): Promise<string> {
|
|
249
300
|
const settings = await this.setting.getSettingValues([
|
|
@@ -264,7 +315,7 @@ export class ChallengeService {
|
|
|
264
315
|
},
|
|
265
316
|
});
|
|
266
317
|
|
|
267
|
-
return
|
|
318
|
+
return token;
|
|
268
319
|
}
|
|
269
320
|
|
|
270
321
|
private resolveAppUrl(settings: Record<string, any>, app?: string): string {
|
|
@@ -338,7 +389,7 @@ export class ChallengeService {
|
|
|
338
389
|
return { success: true }
|
|
339
390
|
}
|
|
340
391
|
|
|
341
|
-
async sendMfaCodeByEmail(locale: string, userId: number, email: string) {
|
|
392
|
+
async sendMfaCodeByEmail(locale: string, userId: number, email: string, origin?: string) {
|
|
342
393
|
const user = await this.prisma.user.findUnique({
|
|
343
394
|
where: { id: userId },
|
|
344
395
|
select: { name: true }
|
|
@@ -382,7 +433,8 @@ export class ChallengeService {
|
|
|
382
433
|
variables: {
|
|
383
434
|
code,
|
|
384
435
|
name: user.name,
|
|
385
|
-
}
|
|
436
|
+
},
|
|
437
|
+
app: await this.resolveAppKeyFromOrigin(origin),
|
|
386
438
|
}
|
|
387
439
|
);
|
|
388
440
|
|
|
@@ -413,7 +465,7 @@ export class ChallengeService {
|
|
|
413
465
|
return true;
|
|
414
466
|
}
|
|
415
467
|
|
|
416
|
-
async sendMfaCodeToMultipleEmails(locale: string, userId: number, emails: string[]) {
|
|
468
|
+
async sendMfaCodeToMultipleEmails(locale: string, userId: number, emails: string[], origin?: string) {
|
|
417
469
|
|
|
418
470
|
if (!emails || emails.length === 0) {
|
|
419
471
|
return { success: true };
|
|
@@ -455,8 +507,10 @@ export class ChallengeService {
|
|
|
455
507
|
}
|
|
456
508
|
});
|
|
457
509
|
|
|
510
|
+
const app = await this.resolveAppKeyFromOrigin(origin);
|
|
511
|
+
|
|
458
512
|
for (const email of emails) {
|
|
459
|
-
|
|
513
|
+
|
|
460
514
|
this.mail.sendTemplatedMail(
|
|
461
515
|
locale,
|
|
462
516
|
{
|
|
@@ -465,7 +519,8 @@ export class ChallengeService {
|
|
|
465
519
|
variables: {
|
|
466
520
|
code,
|
|
467
521
|
name: user.name,
|
|
468
|
-
}
|
|
522
|
+
},
|
|
523
|
+
app,
|
|
469
524
|
}
|
|
470
525
|
);
|
|
471
526
|
}
|