@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,363 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PrismaService,
|
|
3
|
+
user_account_provider_52222e2ecb_enum,
|
|
4
|
+
} from '@hed-hog/api-prisma';
|
|
5
|
+
import { HttpService } from '@nestjs/axios';
|
|
6
|
+
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
|
7
|
+
import { lastValueFrom } from 'rxjs';
|
|
8
|
+
import { buildOAuthConfigFromIntegration } from '../integration-profile/integration-profile.utils';
|
|
9
|
+
import { SecurityService } from '../security/security.service';
|
|
10
|
+
import { SettingService } from '../setting/setting.service';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Reasons a brokered access token could not be produced. Consumers (e.g. the
|
|
14
|
+
* `microsoft` library) map these to HTTP responses so the UI can react — most
|
|
15
|
+
* importantly, `not_connected`/`refresh_revoked` should prompt the user to
|
|
16
|
+
* (re)connect their account, and `missing_scope` to re-consent.
|
|
17
|
+
*/
|
|
18
|
+
export type OAuthTokenBrokerReason =
|
|
19
|
+
| 'not_connected'
|
|
20
|
+
| 'refresh_revoked'
|
|
21
|
+
| 'missing_scope'
|
|
22
|
+
| 'provider_not_configured'
|
|
23
|
+
| 'upstream_failure';
|
|
24
|
+
|
|
25
|
+
export class OAuthTokenBrokerError extends Error {
|
|
26
|
+
constructor(
|
|
27
|
+
readonly reason: OAuthTokenBrokerReason,
|
|
28
|
+
message: string,
|
|
29
|
+
readonly provider: string,
|
|
30
|
+
readonly cause?: unknown,
|
|
31
|
+
) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = 'OAuthTokenBrokerError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface BrokeredAccessToken {
|
|
38
|
+
accessToken: string;
|
|
39
|
+
/** Absolute expiry of the access token. */
|
|
40
|
+
expiresAt: Date;
|
|
41
|
+
/** Scopes granted to the stored refresh token (best-effort, from `user_account.scopes`). */
|
|
42
|
+
scopes: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ProviderTokenConfig {
|
|
46
|
+
tokenUrl: string;
|
|
47
|
+
clientId: string;
|
|
48
|
+
clientSecret: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface CacheEntry {
|
|
52
|
+
accessToken: string;
|
|
53
|
+
expiresAt: Date;
|
|
54
|
+
scopes: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Exchanges a user's stored (encrypted) OAuth **refresh token** for a fresh
|
|
59
|
+
* **access token**, so the backend can call provider APIs (Microsoft Graph,
|
|
60
|
+
* Google, ...) on that user's behalf — the "delegated" access mode.
|
|
61
|
+
*
|
|
62
|
+
* The refresh token is written by {@link OAuthService} during login/connect
|
|
63
|
+
* (encrypted with {@link SecurityService.encrypt} and stored as bytes in
|
|
64
|
+
* `user_account.refresh_token`); this service reads it back, decrypts it, runs
|
|
65
|
+
* a `grant_type=refresh_token` exchange, persists the rotated refresh token,
|
|
66
|
+
* and caches the access token in memory until shortly before it expires.
|
|
67
|
+
*
|
|
68
|
+
* It intentionally reuses the SAME `integration_profile` credentials already
|
|
69
|
+
* configured for OAuth login — no new provider, table, or setting is introduced.
|
|
70
|
+
*/
|
|
71
|
+
@Injectable()
|
|
72
|
+
export class OAuthTokenBrokerService {
|
|
73
|
+
private readonly logger = new Logger(OAuthTokenBrokerService.name);
|
|
74
|
+
|
|
75
|
+
/** access-token cache keyed by `${provider}:${userId}`. */
|
|
76
|
+
private readonly cache = new Map<string, CacheEntry>();
|
|
77
|
+
|
|
78
|
+
/** Refresh a bit early so an in-flight request never rides an about-to-expire token. */
|
|
79
|
+
private static readonly EXPIRY_SKEW_MS = 60 * 1000;
|
|
80
|
+
|
|
81
|
+
constructor(
|
|
82
|
+
private readonly http: HttpService,
|
|
83
|
+
@Inject(forwardRef(() => SettingService))
|
|
84
|
+
private readonly setting: SettingService,
|
|
85
|
+
private readonly prisma: PrismaService,
|
|
86
|
+
private readonly security: SecurityService,
|
|
87
|
+
) {}
|
|
88
|
+
|
|
89
|
+
/** Normalizes a provider to the enum form stored in `user_account.provider`. */
|
|
90
|
+
private toAccountProvider(
|
|
91
|
+
provider: string,
|
|
92
|
+
): user_account_provider_52222e2ecb_enum {
|
|
93
|
+
return String(provider)
|
|
94
|
+
.toLowerCase()
|
|
95
|
+
.replace(/-/g, '_') as user_account_provider_52222e2ecb_enum;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Setting slug that points at the integration profile for this provider. */
|
|
99
|
+
private profileSettingSlug(provider: string): string | null {
|
|
100
|
+
switch (String(provider).toLowerCase().replace(/_/g, '-')) {
|
|
101
|
+
case 'microsoft-entra-id':
|
|
102
|
+
return 'oauth-microsoft-entra-id-profile-id';
|
|
103
|
+
case 'microsoft':
|
|
104
|
+
return 'oauth-microsoft-profile-id';
|
|
105
|
+
case 'google':
|
|
106
|
+
return 'oauth-google-profile-id';
|
|
107
|
+
default:
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async resolveProviderConfig(
|
|
113
|
+
provider: string,
|
|
114
|
+
): Promise<ProviderTokenConfig> {
|
|
115
|
+
const settingSlug = this.profileSettingSlug(provider);
|
|
116
|
+
if (!settingSlug) {
|
|
117
|
+
throw new OAuthTokenBrokerError(
|
|
118
|
+
'provider_not_configured',
|
|
119
|
+
`Token broker does not support provider "${provider}".`,
|
|
120
|
+
provider,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const settings = await this.setting.getSettingValues([settingSlug]);
|
|
125
|
+
const profileSlug = String(settings[settingSlug] ?? '').trim();
|
|
126
|
+
if (!profileSlug) {
|
|
127
|
+
throw new OAuthTokenBrokerError(
|
|
128
|
+
'provider_not_configured',
|
|
129
|
+
`OAuth profile for "${provider}" is not configured.`,
|
|
130
|
+
provider,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const profile = await this.prisma.integration_profile.findUnique({
|
|
135
|
+
where: { slug: profileSlug },
|
|
136
|
+
include: { integration_provider: { select: { slug: true } } },
|
|
137
|
+
});
|
|
138
|
+
if (!profile) {
|
|
139
|
+
throw new OAuthTokenBrokerError(
|
|
140
|
+
'provider_not_configured',
|
|
141
|
+
`OAuth profile "${profileSlug}" for "${provider}" was not found.`,
|
|
142
|
+
provider,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const { clientId, clientSecret, tenantId } = buildOAuthConfigFromIntegration(
|
|
147
|
+
profile.integration_provider.slug,
|
|
148
|
+
profile.config,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const normalized = String(provider).toLowerCase().replace(/_/g, '-');
|
|
152
|
+
let tokenUrl: string;
|
|
153
|
+
if (normalized === 'microsoft-entra-id') {
|
|
154
|
+
if (!tenantId) {
|
|
155
|
+
throw new OAuthTokenBrokerError(
|
|
156
|
+
'provider_not_configured',
|
|
157
|
+
'Microsoft Entra ID OAuth profile is missing tenant_id.',
|
|
158
|
+
provider,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
|
162
|
+
} else if (normalized === 'microsoft') {
|
|
163
|
+
tokenUrl = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
|
|
164
|
+
} else {
|
|
165
|
+
// google
|
|
166
|
+
tokenUrl = 'https://oauth2.googleapis.com/token';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { tokenUrl, clientId, clientSecret };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private async loadUserAccount(userId: number, provider: string) {
|
|
173
|
+
const account = await this.prisma.user_account.findFirst({
|
|
174
|
+
where: {
|
|
175
|
+
user_id: userId,
|
|
176
|
+
provider: this.toAccountProvider(provider),
|
|
177
|
+
},
|
|
178
|
+
select: {
|
|
179
|
+
id: true,
|
|
180
|
+
refresh_token: true,
|
|
181
|
+
scopes: true,
|
|
182
|
+
token_expires_at: true,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (!account || !account.refresh_token) {
|
|
187
|
+
throw new OAuthTokenBrokerError(
|
|
188
|
+
'not_connected',
|
|
189
|
+
`User ${userId} has no connected "${provider}" account.`,
|
|
190
|
+
provider,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return account;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private parseScopes(scopes?: string | null): string[] {
|
|
198
|
+
return String(scopes ?? '')
|
|
199
|
+
.split(/[\s,]+/)
|
|
200
|
+
.map((scope) => scope.trim())
|
|
201
|
+
.filter(Boolean);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Scopes granted to the stored refresh token, from `user_account.scopes`. */
|
|
205
|
+
async getGrantedScopes(userId: number, provider: string): Promise<string[]> {
|
|
206
|
+
const account = await this.prisma.user_account.findFirst({
|
|
207
|
+
where: { user_id: userId, provider: this.toAccountProvider(provider) },
|
|
208
|
+
select: { scopes: true },
|
|
209
|
+
});
|
|
210
|
+
return this.parseScopes(account?.scopes);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Whether the user has this provider connected with a usable refresh token. */
|
|
214
|
+
async isConnected(userId: number, provider: string): Promise<boolean> {
|
|
215
|
+
const account = await this.prisma.user_account.findFirst({
|
|
216
|
+
where: { user_id: userId, provider: this.toAccountProvider(provider) },
|
|
217
|
+
select: { refresh_token: true },
|
|
218
|
+
});
|
|
219
|
+
return Boolean(account?.refresh_token);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private cacheKey(userId: number, provider: string): string {
|
|
223
|
+
return `${String(provider).toLowerCase()}:${userId}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Returns a fresh access token for the user, refreshing (and rotating the
|
|
228
|
+
* stored refresh token) when the cache is cold or about to expire.
|
|
229
|
+
*
|
|
230
|
+
* @throws {OAuthTokenBrokerError} with a typed `reason` on any failure.
|
|
231
|
+
*/
|
|
232
|
+
async getAccessToken(
|
|
233
|
+
userId: number,
|
|
234
|
+
provider: string,
|
|
235
|
+
): Promise<BrokeredAccessToken> {
|
|
236
|
+
const key = this.cacheKey(userId, provider);
|
|
237
|
+
const cached = this.cache.get(key);
|
|
238
|
+
if (
|
|
239
|
+
cached &&
|
|
240
|
+
cached.expiresAt.getTime() - Date.now() > OAuthTokenBrokerService.EXPIRY_SKEW_MS
|
|
241
|
+
) {
|
|
242
|
+
return { ...cached };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const [config, account] = await Promise.all([
|
|
246
|
+
this.resolveProviderConfig(provider),
|
|
247
|
+
this.loadUserAccount(userId, provider),
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
let decryptedRefreshToken: string;
|
|
251
|
+
try {
|
|
252
|
+
decryptedRefreshToken = this.security.decrypt(account.refresh_token.toString());
|
|
253
|
+
} catch (error) {
|
|
254
|
+
throw new OAuthTokenBrokerError(
|
|
255
|
+
'refresh_revoked',
|
|
256
|
+
`Failed to decrypt stored refresh token for "${provider}".`,
|
|
257
|
+
provider,
|
|
258
|
+
error,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const grantedScopes = this.parseScopes(account.scopes);
|
|
263
|
+
const scopeParam = grantedScopes.includes('offline_access')
|
|
264
|
+
? grantedScopes
|
|
265
|
+
: [...grantedScopes, 'offline_access'];
|
|
266
|
+
|
|
267
|
+
const body = new URLSearchParams({
|
|
268
|
+
client_id: config.clientId,
|
|
269
|
+
client_secret: config.clientSecret,
|
|
270
|
+
grant_type: 'refresh_token',
|
|
271
|
+
refresh_token: decryptedRefreshToken,
|
|
272
|
+
});
|
|
273
|
+
if (scopeParam.length > 1) {
|
|
274
|
+
body.set('scope', scopeParam.join(' '));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let token: {
|
|
278
|
+
access_token: string;
|
|
279
|
+
refresh_token?: string;
|
|
280
|
+
expires_in?: number;
|
|
281
|
+
scope?: string;
|
|
282
|
+
};
|
|
283
|
+
try {
|
|
284
|
+
const response = await lastValueFrom(
|
|
285
|
+
this.http.post(config.tokenUrl, body.toString(), {
|
|
286
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
287
|
+
}),
|
|
288
|
+
);
|
|
289
|
+
token = response.data;
|
|
290
|
+
} catch (error: any) {
|
|
291
|
+
const providerError = error?.response?.data?.error;
|
|
292
|
+
if (providerError === 'invalid_grant') {
|
|
293
|
+
// Refresh token revoked/expired — the user must reconnect.
|
|
294
|
+
this.cache.delete(key);
|
|
295
|
+
throw new OAuthTokenBrokerError(
|
|
296
|
+
'refresh_revoked',
|
|
297
|
+
`Refresh token for "${provider}" is no longer valid. Reconnect the account.`,
|
|
298
|
+
provider,
|
|
299
|
+
error,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
this.logger.warn(
|
|
303
|
+
`Token refresh failed: provider=${provider} userId=${userId} error=${providerError ?? error?.message ?? 'unknown'}`,
|
|
304
|
+
);
|
|
305
|
+
throw new OAuthTokenBrokerError(
|
|
306
|
+
'upstream_failure',
|
|
307
|
+
`Unable to refresh "${provider}" access token.`,
|
|
308
|
+
provider,
|
|
309
|
+
error,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!token?.access_token) {
|
|
314
|
+
throw new OAuthTokenBrokerError(
|
|
315
|
+
'upstream_failure',
|
|
316
|
+
`Provider "${provider}" returned no access token.`,
|
|
317
|
+
provider,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const expiresAt = new Date(
|
|
322
|
+
Date.now() + (token.expires_in ? token.expires_in * 1000 : 3600 * 1000),
|
|
323
|
+
);
|
|
324
|
+
const resolvedScopes = token.scope
|
|
325
|
+
? this.parseScopes(token.scope)
|
|
326
|
+
: grantedScopes;
|
|
327
|
+
|
|
328
|
+
// Persist the rotated refresh token so the next call starts from the newest
|
|
329
|
+
// one (Microsoft rotates refresh tokens on every exchange).
|
|
330
|
+
if (token.refresh_token && token.refresh_token !== decryptedRefreshToken) {
|
|
331
|
+
try {
|
|
332
|
+
const encrypted = this.security.encrypt(token.refresh_token);
|
|
333
|
+
await this.prisma.user_account.update({
|
|
334
|
+
where: { id: account.id },
|
|
335
|
+
data: {
|
|
336
|
+
refresh_token: Buffer.from(encrypted),
|
|
337
|
+
token_expires_at: expiresAt,
|
|
338
|
+
...(token.scope ? { scopes: resolvedScopes.join(',') } : {}),
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
} catch (error) {
|
|
342
|
+
// A persistence failure must not fail the request — the access token is
|
|
343
|
+
// already valid; log and continue (next refresh reuses the old token).
|
|
344
|
+
this.logger.warn(
|
|
345
|
+
`Failed to persist rotated refresh token: provider=${provider} userId=${userId} error=${error instanceof Error ? error.message : 'unknown'}`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const entry: CacheEntry = {
|
|
351
|
+
accessToken: token.access_token,
|
|
352
|
+
expiresAt,
|
|
353
|
+
scopes: resolvedScopes,
|
|
354
|
+
};
|
|
355
|
+
this.cache.set(key, entry);
|
|
356
|
+
return { ...entry };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Drops any cached access token for a user/provider (e.g. after disconnect). */
|
|
360
|
+
invalidate(userId: number, provider: string): void {
|
|
361
|
+
this.cache.delete(this.cacheKey(userId, provider));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -10,6 +10,7 @@ import { SecurityModule } from '../security/security.module';
|
|
|
10
10
|
import { TokenModule } from '../token/token.module';
|
|
11
11
|
import { UserModule } from '../user/user.module';
|
|
12
12
|
import { OAuthCallbackCoordinatorService } from './oauth-callback-coordinator.service';
|
|
13
|
+
import { OAuthTokenBrokerService } from './oauth-token-broker.service';
|
|
13
14
|
import { OAuthController } from './oauth.controller';
|
|
14
15
|
import { OAuthService } from './oauth.service';
|
|
15
16
|
import { AppleProvider } from './providers/apple.provider';
|
|
@@ -36,6 +37,7 @@ import { MicrosoftProvider } from './providers/microsoft.provider';
|
|
|
36
37
|
controllers: [OAuthController],
|
|
37
38
|
providers: [
|
|
38
39
|
OAuthCallbackCoordinatorService,
|
|
40
|
+
OAuthTokenBrokerService,
|
|
39
41
|
OAuthService,
|
|
40
42
|
GoogleProvider,
|
|
41
43
|
FacebookProvider,
|
|
@@ -45,6 +47,6 @@ import { MicrosoftProvider } from './providers/microsoft.provider';
|
|
|
45
47
|
AppleProvider,
|
|
46
48
|
LinkedinProvider,
|
|
47
49
|
],
|
|
48
|
-
exports: [OAuthService],
|
|
50
|
+
exports: [OAuthService, OAuthTokenBrokerService],
|
|
49
51
|
})
|
|
50
52
|
export class OAuthModule {}
|
|
@@ -50,6 +50,8 @@ type HandleCallbackProps = {
|
|
|
50
50
|
requestOrigin?: string;
|
|
51
51
|
/** Referer header, used as a fallback when the Origin header is absent. */
|
|
52
52
|
requestReferer?: string;
|
|
53
|
+
/** App key that initiated the flow (decoded from the signed web state), used for per-app email branding. */
|
|
54
|
+
app?: string;
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
@Injectable()
|
|
@@ -338,6 +340,7 @@ export class OAuthService {
|
|
|
338
340
|
profile,
|
|
339
341
|
userId,
|
|
340
342
|
includeRefreshTokenInBody: true,
|
|
343
|
+
app: webState?.app,
|
|
341
344
|
});
|
|
342
345
|
})) as { accessToken: string; refreshToken?: string };
|
|
343
346
|
|
|
@@ -365,6 +368,7 @@ export class OAuthService {
|
|
|
365
368
|
profile,
|
|
366
369
|
userId,
|
|
367
370
|
includeRefreshTokenInBody,
|
|
371
|
+
app: webState?.app,
|
|
368
372
|
});
|
|
369
373
|
|
|
370
374
|
this.logger.log(
|
|
@@ -741,12 +745,13 @@ export class OAuthService {
|
|
|
741
745
|
profile,
|
|
742
746
|
userId,
|
|
743
747
|
includeRefreshTokenInBody,
|
|
748
|
+
app,
|
|
744
749
|
}: Omit<HandleCallbackProps, 'code' | 'email' | 'redirectUri' | 'state'> & { profile: any }) {
|
|
745
750
|
switch (type) {
|
|
746
751
|
case 'login':
|
|
747
752
|
return this.handleLogin(res, locale, ipAddress, userAgent, provider, profile, includeRefreshTokenInBody);
|
|
748
753
|
case 'register':
|
|
749
|
-
return this.handleRegister(res, locale, ipAddress, userAgent, provider, profile, includeRefreshTokenInBody);
|
|
754
|
+
return this.handleRegister(res, locale, ipAddress, userAgent, provider, profile, includeRefreshTokenInBody, app);
|
|
750
755
|
case 'connect':
|
|
751
756
|
return this.handleConnect(res, locale, ipAddress, userAgent, provider, profile, userId, includeRefreshTokenInBody);
|
|
752
757
|
default:
|
|
@@ -896,7 +901,7 @@ export class OAuthService {
|
|
|
896
901
|
}
|
|
897
902
|
}
|
|
898
903
|
|
|
899
|
-
private async handleRegister(res: Response, locale: string, ipAddress: string, userAgent: string, provider: user_account_provider_52222e2ecb_enum, profile: any, includeRefreshTokenInBody?: boolean) {
|
|
904
|
+
private async handleRegister(res: Response, locale: string, ipAddress: string, userAgent: string, provider: user_account_provider_52222e2ecb_enum, profile: any, includeRefreshTokenInBody?: boolean, app?: string) {
|
|
900
905
|
const existingUser = await this.prisma.user_identifier.findFirst({
|
|
901
906
|
where: {
|
|
902
907
|
type: 'email',
|
|
@@ -959,6 +964,7 @@ export class OAuthService {
|
|
|
959
964
|
email: profile.email,
|
|
960
965
|
slug: 'auth-sign-up',
|
|
961
966
|
variables: { name: profile.name },
|
|
967
|
+
app,
|
|
962
968
|
});
|
|
963
969
|
|
|
964
970
|
const pictureURL = profile.picture?.data?.url || profile.picture;
|
package/src/pdf/pdf.service.ts
CHANGED
|
@@ -229,6 +229,81 @@ export class PdfService {
|
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
// ── HTML → PNG image ──────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Renders an HTML string to a PNG image buffer via a Playwright/Chromium
|
|
236
|
+
* screenshot. Shares the same layout contract as {@link generatePdfFromHtml}
|
|
237
|
+
* (the viewport matches `width`/`height`), so the image is a pixel-faithful
|
|
238
|
+
* preview of what the PDF would produce for the same HTML — used for
|
|
239
|
+
* certificate thumbnails on the web/app.
|
|
240
|
+
*/
|
|
241
|
+
async generateImageFromHtml(html: string, options: PdfGenerateOptions = {}): Promise<Buffer> {
|
|
242
|
+
const executablePath = this.configService.get<string>('CHROMIUM_EXECUTABLE_PATH');
|
|
243
|
+
const noSandbox = this.configService.get<string>('CHROMIUM_NO_SANDBOX') === 'true';
|
|
244
|
+
|
|
245
|
+
const launchArgs = noSandbox
|
|
246
|
+
? ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
|
|
247
|
+
: [];
|
|
248
|
+
|
|
249
|
+
const width = parsePxSize(options.width, 794);
|
|
250
|
+
const height = parsePxSize(options.height, 1123);
|
|
251
|
+
|
|
252
|
+
let browser: any = null;
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const importPlaywright = new Function(
|
|
256
|
+
'moduleName',
|
|
257
|
+
'return import(moduleName);',
|
|
258
|
+
) as (moduleName: string) => Promise<any>;
|
|
259
|
+
|
|
260
|
+
const playwright = await importPlaywright('playwright');
|
|
261
|
+
|
|
262
|
+
browser = await playwright.chromium.launch({
|
|
263
|
+
headless: true,
|
|
264
|
+
...(executablePath ? { executablePath } : {}),
|
|
265
|
+
args: launchArgs,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const page = await browser.newPage();
|
|
269
|
+
await page.setViewportSize({ width, height });
|
|
270
|
+
await page.setContent(html, { waitUntil: 'networkidle' });
|
|
271
|
+
|
|
272
|
+
const imageBuffer = await page.screenshot({
|
|
273
|
+
type: 'png',
|
|
274
|
+
clip: { x: 0, y: 0, width, height },
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return Buffer.from(imageBuffer);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
280
|
+
const errorStack = error instanceof Error ? (error.stack ?? errorMessage) : String(error);
|
|
281
|
+
|
|
282
|
+
const missingRuntime =
|
|
283
|
+
/Cannot find package ['"]playwright['"]|Cannot find module ['"]playwright['"]|Executable doesn't exist|browserType\.launch|Failed to launch|Please run the following command/i.test(
|
|
284
|
+
errorMessage,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
this.logger.error(`Image generation failed. ${errorMessage}`, errorStack);
|
|
288
|
+
|
|
289
|
+
if (missingRuntime) {
|
|
290
|
+
throw new InternalServerErrorException(
|
|
291
|
+
'Playwright runtime is not available. Run `pnpm --filter api run playwright:install` in the API environment.',
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
throw new InternalServerErrorException(`Image generation failed: ${errorMessage}`);
|
|
296
|
+
} finally {
|
|
297
|
+
if (browser) {
|
|
298
|
+
try {
|
|
299
|
+
await browser.close();
|
|
300
|
+
} catch {
|
|
301
|
+
// Ignore close errors
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
232
307
|
// ── Template engine ───────────────────────────────────────────────────────
|
|
233
308
|
|
|
234
309
|
/**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { User } from '@hed-hog/api';
|
|
2
|
+
import { Controller, Get, Post, Query } from '@nestjs/common';
|
|
3
|
+
import { PresenceService } from './presence.service';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Presence endpoints — authenticated (no @Role): any signed-in user may send
|
|
7
|
+
* their own heartbeat and read presence for a set of users.
|
|
8
|
+
*/
|
|
9
|
+
@Controller('presence')
|
|
10
|
+
export class PresenceController {
|
|
11
|
+
constructor(private readonly presence: PresenceService) {}
|
|
12
|
+
|
|
13
|
+
/** Browser heartbeat: marks the signed-in user as online in the app. */
|
|
14
|
+
@Post('heartbeat')
|
|
15
|
+
async heartbeat(@User() user: any) {
|
|
16
|
+
await this.presence.heartbeat(Number(user?.id) || 0);
|
|
17
|
+
return { ok: true };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Combined presence for `userIds` (comma-separated). */
|
|
21
|
+
@Get()
|
|
22
|
+
async getStatuses(@Query('userIds') userIds?: string) {
|
|
23
|
+
const ids = String(userIds ?? '')
|
|
24
|
+
.split(',')
|
|
25
|
+
.map((s) => Number(s.trim()))
|
|
26
|
+
.filter((n) => Number.isInteger(n) && n > 0);
|
|
27
|
+
return this.presence.getStatuses(ids);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
+
import { OnEvent } from '@nestjs/event-emitter';
|
|
3
|
+
import { Cron } from '@nestjs/schedule';
|
|
4
|
+
import { RealtimeService } from '../realtime/realtime.service';
|
|
5
|
+
import { REALTIME_EVENTS } from '../realtime/realtime.gateway';
|
|
6
|
+
import { PresenceService } from './presence.service';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Bridges the generic realtime layer to presence: a live socket IS the app
|
|
10
|
+
* heartbeat. On (first) connect the user goes online and the change is pushed;
|
|
11
|
+
* while connected, a cron keeps the heartbeat fresh (so it never lapses under
|
|
12
|
+
* the 60s window); on disconnect the new status is pushed.
|
|
13
|
+
*/
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class PresenceListener {
|
|
16
|
+
private readonly logger = new Logger(PresenceListener.name);
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
private readonly presence: PresenceService,
|
|
20
|
+
private readonly realtime: RealtimeService,
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
@OnEvent(REALTIME_EVENTS.userConnected)
|
|
24
|
+
async onConnected(payload: { userId: number; firstConnection: boolean }) {
|
|
25
|
+
await this.presence.heartbeat(payload.userId);
|
|
26
|
+
// Only push when the user actually transitioned to online (first socket).
|
|
27
|
+
if (payload.firstConnection) {
|
|
28
|
+
await this.presence.pushStatus(payload.userId).catch(() => undefined);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@OnEvent(REALTIME_EVENTS.userDisconnected)
|
|
33
|
+
async onDisconnected(payload: { userId: number; remainingLocalSockets: number }) {
|
|
34
|
+
// Recompute/push; if the user still has sockets elsewhere the heartbeat keeps
|
|
35
|
+
// them online, otherwise they go offline once the heartbeat window elapses.
|
|
36
|
+
if (payload.remainingLocalSockets === 0) {
|
|
37
|
+
await this.presence.pushStatus(payload.userId).catch(() => undefined);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Keeps the app heartbeat fresh for every locally-connected user. A connected
|
|
43
|
+
* socket must stay "online" past the 60s heartbeat window, so refresh every
|
|
44
|
+
* 30s. Silent (no push) — nothing changed.
|
|
45
|
+
*/
|
|
46
|
+
@Cron('*/30 * * * * *')
|
|
47
|
+
async refreshHeartbeats() {
|
|
48
|
+
const userIds = this.realtime.getLocallyConnectedUserIds();
|
|
49
|
+
for (const userId of userIds) {
|
|
50
|
+
await this.presence.heartbeat(userId).catch(() => undefined);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PrismaModule } from '@hed-hog/api-prisma';
|
|
2
|
+
import { forwardRef, Module } from '@nestjs/common';
|
|
3
|
+
import { RealtimeModule } from '../realtime/realtime.module';
|
|
4
|
+
import { SettingModule } from '../setting/setting.module';
|
|
5
|
+
import { PresenceController } from './presence.controller';
|
|
6
|
+
import { PresenceListener } from './presence.listener';
|
|
7
|
+
import { PresenceService } from './presence.service';
|
|
8
|
+
|
|
9
|
+
@Module({
|
|
10
|
+
imports: [
|
|
11
|
+
forwardRef(() => PrismaModule),
|
|
12
|
+
forwardRef(() => SettingModule),
|
|
13
|
+
forwardRef(() => RealtimeModule),
|
|
14
|
+
],
|
|
15
|
+
controllers: [PresenceController],
|
|
16
|
+
providers: [PresenceService, PresenceListener],
|
|
17
|
+
exports: [PresenceService],
|
|
18
|
+
})
|
|
19
|
+
export class PresenceModule {}
|