@hed-hog/core 0.0.368 → 0.0.371
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/LICENSE.md +21 -0
- package/README.md +2 -0
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +10 -7
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/app-command/app-command.module.d.ts +3 -0
- package/dist/app-command/app-command.module.d.ts.map +1 -0
- package/dist/app-command/app-command.module.js +22 -0
- package/dist/app-command/app-command.module.js.map +1 -0
- package/dist/app-command/app-command.registry.d.ts +22 -0
- package/dist/app-command/app-command.registry.d.ts.map +1 -0
- package/dist/{webhook-integration/webhook-command.registry.js → app-command/app-command.registry.js} +6 -6
- package/dist/app-command/app-command.registry.js.map +1 -0
- package/dist/auth/auth.controller.d.ts +2 -2
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +20 -7
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +3 -3
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +10 -36
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/auth/dto/forget.dto.d.ts +1 -0
- package/dist/auth/dto/forget.dto.d.ts.map +1 -1
- package/dist/auth/dto/forget.dto.js +5 -0
- package/dist/auth/dto/forget.dto.js.map +1 -1
- package/dist/auth/guards/auth.guard.d.ts.map +1 -1
- package/dist/auth/guards/auth.guard.js +4 -1
- package/dist/auth/guards/auth.guard.js.map +1 -1
- package/dist/challenge/challenge.service.d.ts +8 -7
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +27 -19
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/file/dto/upload.dto.d.ts +3 -0
- package/dist/file/dto/upload.dto.d.ts.map +1 -1
- package/dist/file/dto/upload.dto.js +22 -0
- package/dist/file/dto/upload.dto.js.map +1 -1
- package/dist/file/file.controller.d.ts +2 -1
- package/dist/file/file.controller.d.ts.map +1 -1
- package/dist/file/file.controller.js +29 -16
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.service.d.ts +51 -1
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +250 -18
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/image-optimization.service.d.ts +17 -4
- package/dist/file/image-optimization.service.d.ts.map +1 -1
- package/dist/file/image-optimization.service.js +101 -46
- package/dist/file/image-optimization.service.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +15 -7
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/file/provider/spaces.provider.d.ts.map +1 -1
- package/dist/file/provider/spaces.provider.js +18 -8
- package/dist/file/provider/spaces.provider.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/integration/services/event-subscriber.registry.d.ts +5 -0
- package/dist/integration/services/event-subscriber.registry.d.ts.map +1 -1
- package/dist/integration/services/event-subscriber.registry.js +15 -0
- package/dist/integration/services/event-subscriber.registry.js.map +1 -1
- package/dist/integration/services/outbox.processor.d.ts.map +1 -1
- package/dist/integration/services/outbox.processor.js +2 -4
- package/dist/integration/services/outbox.processor.js.map +1 -1
- package/dist/integration/services/outbox.service.d.ts +4 -2
- package/dist/integration/services/outbox.service.d.ts.map +1 -1
- package/dist/integration/services/outbox.service.js +26 -15
- package/dist/integration/services/outbox.service.js.map +1 -1
- package/dist/integration-profile/integration-credential-crypto.service.d.ts +43 -0
- package/dist/integration-profile/integration-credential-crypto.service.d.ts.map +1 -0
- package/dist/integration-profile/integration-credential-crypto.service.js +142 -0
- package/dist/integration-profile/integration-credential-crypto.service.js.map +1 -0
- package/dist/integration-profile/integration-profile.controller.d.ts +6 -7
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.controller.js +4 -3
- package/dist/integration-profile/integration-profile.controller.js.map +1 -1
- package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.module.js +5 -2
- package/dist/integration-profile/integration-profile.module.js.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts +16 -0
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -0
- package/dist/integration-profile/integration-profile.secrets.js +56 -0
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -0
- package/dist/integration-profile/integration-profile.service.d.ts +125 -8
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +257 -68
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.d.ts +17 -0
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +31 -0
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/language/en.json +178 -0
- package/dist/language/pt.json +178 -0
- package/dist/mail/mail.controller.d.ts.map +1 -1
- package/dist/mail/mail.controller.js +5 -28
- package/dist/mail/mail.controller.js.map +1 -1
- package/dist/mail/mail.module.js +18 -18
- package/dist/mail/mail.module.js.map +1 -1
- package/dist/mail/mail.service.d.ts +13 -2
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +25 -15
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +6 -5
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/oauth/providers/facebook.provider.js +5 -5
- package/dist/oauth/providers/facebook.provider.js.map +1 -1
- package/dist/oauth/providers/github.provider.js +5 -5
- package/dist/oauth/providers/github.provider.js.map +1 -1
- package/dist/oauth/providers/google.provider.js +5 -5
- package/dist/oauth/providers/google.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.js +5 -5
- package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft.provider.js +5 -5
- package/dist/oauth/providers/microsoft.provider.js.map +1 -1
- package/dist/pdf/pdf.service.d.ts +4 -0
- package/dist/pdf/pdf.service.d.ts.map +1 -1
- package/dist/pdf/pdf.service.js +16 -9
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/profile/profile.controller.d.ts +7 -7
- package/dist/profile/profile.controller.d.ts.map +1 -1
- package/dist/profile/profile.controller.js +28 -21
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/profile/profile.service.d.ts +7 -7
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +21 -25
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/setting/setting.service.d.ts +1 -0
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +15 -2
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +2 -1
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +9 -0
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +13 -0
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +7 -0
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +19 -0
- package/dist/user/user.service.js.map +1 -1
- package/dist/utils/webauthn-origin.d.ts +11 -0
- package/dist/utils/webauthn-origin.d.ts.map +1 -0
- package/dist/utils/webauthn-origin.js +37 -0
- package/dist/utils/webauthn-origin.js.map +1 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts +13 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.js +29 -0
- package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.module.js +3 -3
- package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts +18 -2
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +110 -6
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/hedhog/data/integration_event_catalog.yaml +118 -118
- package/hedhog/data/integration_provider.yaml +9 -0
- package/hedhog/data/integration_type.yaml +6 -0
- package/hedhog/data/menu.yaml +6 -0
- package/hedhog/data/role.yaml +7 -0
- package/hedhog/data/route.yaml +192 -207
- package/hedhog/data/setting_group.yaml +119 -13
- package/hedhog/frontend/app/access-log/page.tsx.ejs +35 -3
- package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +36 -3
- package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +39 -4
- package/hedhog/frontend/app/account/components/tokens.tsx.ejs +39 -4
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +35 -2
- package/hedhog/frontend/app/configurations/[slug]/components/byte-size-input.tsx.ejs +160 -0
- package/hedhog/frontend/app/configurations/[slug]/components/duration-input.tsx.ejs +157 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +244 -63
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +33 -1
- package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +35 -3
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +36 -4
- package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +35 -3
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +36 -4
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +35 -3
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +141 -29
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +118 -11
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +37 -4
- package/hedhog/frontend/app/menu/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/preferences/page.tsx.ejs +77 -0
- package/hedhog/frontend/app/roles/menus.tsx.ejs +35 -3
- package/hedhog/frontend/app/roles/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/roles/routes.tsx.ejs +36 -4
- package/hedhog/frontend/app/roles/users.tsx.ejs +36 -4
- package/hedhog/frontend/app/routes/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/setting-list/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/users/page.tsx.ejs +51 -21
- package/hedhog/frontend/messages/en.json +69 -4
- package/hedhog/frontend/messages/pt.json +69 -4
- package/hedhog/table/event_webhook.yaml +55 -55
- package/hedhog/table/event_webhook_delivery_log.yaml +66 -66
- package/hedhog/table/event_webhook_event.yaml +26 -26
- package/hedhog/table/integration_event_catalog.yaml +41 -41
- package/hedhog/table/mcp_conversation.yaml +12 -12
- package/hedhog/table/mcp_conversation_message.yaml +23 -23
- package/hedhog/table/mcp_tool_call_log.yaml +22 -22
- package/hedhog/table/route.yaml +33 -33
- package/hedhog/table/setting.yaml +2 -0
- package/hedhog/table/webhook_call_log.yaml +3 -0
- package/hedhog/table/webhook_integration.yaml +43 -43
- package/package.json +9 -4
- package/src/ai/ai.service.ts +9 -6
- package/src/app-command/app-command.module.ts +9 -0
- package/src/{webhook-integration/webhook-command.registry.ts → app-command/app-command.registry.ts} +9 -9
- package/src/auth/auth.controller.ts +20 -4
- package/src/auth/auth.service.ts +10 -36
- package/src/auth/dto/forget.dto.ts +5 -1
- package/src/auth/guards/auth.guard.ts +4 -1
- package/src/challenge/challenge.service.ts +30 -19
- package/src/file/dto/upload.dto.ts +25 -1
- package/src/file/file.controller.ts +31 -18
- package/src/file/file.service.ts +341 -16
- package/src/file/image-optimization.service.ts +111 -46
- package/src/file/provider/s3.provider.ts +16 -7
- package/src/file/provider/spaces.provider.ts +15 -5
- package/src/index.ts +4 -1
- package/src/integration/services/event-subscriber.registry.ts +15 -0
- package/src/integration/services/outbox.processor.ts +2 -5
- package/src/integration/services/outbox.service.ts +35 -17
- package/src/integration/types/event.types.ts +4 -4
- package/src/integration-profile/integration-credential-crypto.service.ts +146 -0
- package/src/integration-profile/integration-profile.controller.ts +2 -0
- package/src/integration-profile/integration-profile.module.ts +5 -2
- package/src/integration-profile/integration-profile.secrets.ts +51 -0
- package/src/integration-profile/integration-profile.service.ts +335 -54
- package/src/integration-profile/integration-profile.utils.ts +56 -0
- package/src/language/en.json +5 -1
- package/src/language/pt.json +5 -1
- package/src/mail/mail.controller.ts +5 -29
- package/src/mail/mail.module.ts +5 -5
- package/src/mail/mail.service.ts +37 -13
- package/src/mcp/decorators/mcp-tool.decorator.ts +7 -7
- package/src/mcp/guards/mcp-permission.guard.ts +51 -51
- package/src/mcp/interfaces/mcp-tool-definition.interface.ts +6 -6
- package/src/mcp/mcp-server.service.ts +87 -87
- package/src/mcp/types/mcp-context.type.ts +6 -6
- package/src/mcp-chat/dto/create-conversation.dto.ts +7 -7
- package/src/mcp-chat/dto/send-message.dto.ts +7 -7
- package/src/mcp-chat/mcp-chat.service.ts +5 -5
- package/src/notification/events/notification-stale-progress-check.event.ts +5 -5
- package/src/oauth/oauth-callback-coordinator.service.spec.ts +159 -0
- package/src/oauth/providers/facebook.provider.ts +3 -3
- package/src/oauth/providers/github.provider.ts +3 -3
- package/src/oauth/providers/google.provider.ts +3 -3
- package/src/oauth/providers/microsoft-entra-id.provider.ts +3 -3
- package/src/oauth/providers/microsoft.provider.ts +3 -3
- package/src/pdf/pdf.service.ts +19 -6
- package/src/profile/profile.controller.ts +21 -14
- package/src/profile/profile.service.ts +23 -20
- package/src/route/dto/create.dto.ts +32 -32
- package/src/route/dto/update.dto.ts +31 -31
- package/src/security/security.service.spec.ts +222 -0
- package/src/setting/setting.service.ts +15 -2
- package/src/token/token.service.ts +2 -1
- package/src/user/user.controller.ts +26 -16
- package/src/user/user.service.ts +25 -0
- package/src/utils/webauthn-origin.ts +38 -0
- package/src/webhook-integration/integration-event-catalog.controller.ts +50 -50
- package/src/webhook-integration/integration-event-catalog.service.ts +117 -117
- package/src/webhook-integration/webhook-integration.controller.ts +23 -0
- package/src/webhook-integration/webhook-integration.module.ts +3 -3
- package/src/webhook-integration/webhook-integration.service.ts +115 -5
- package/dist/webhook-integration/webhook-command.registry.d.ts +0 -22
- package/dist/webhook-integration/webhook-command.registry.d.ts.map +0 -1
- package/dist/webhook-integration/webhook-command.registry.js.map +0 -1
- package/src/menu/menu.service.spec.ts +0 -247
- package/src/route/route.service.spec.ts +0 -300
- package/src/screen/screen.service.spec.ts +0 -298
- package/src/user/user.service.spec.ts +0 -294
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from '@jest/globals';
|
|
2
|
+
import { OAuthCallbackCoordinatorService } from './oauth-callback-coordinator.service';
|
|
3
|
+
import {
|
|
4
|
+
OAuthCallbackAlreadyProcessedError,
|
|
5
|
+
OAuthCallbackInProgressError,
|
|
6
|
+
OAuthProviderException,
|
|
7
|
+
} from './oauth.errors';
|
|
8
|
+
|
|
9
|
+
const makeService = (prisma: any) => new OAuthCallbackCoordinatorService(prisma as any);
|
|
10
|
+
|
|
11
|
+
const noLockPrisma = (overrides: any = {}) => ({
|
|
12
|
+
isPostgres: jest.fn().mockReturnValue(false),
|
|
13
|
+
isMysql: jest.fn().mockReturnValue(false),
|
|
14
|
+
getProvider: jest.fn().mockReturnValue('sqlite'),
|
|
15
|
+
...overrides,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('OAuthCallbackCoordinatorService', () => {
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
jest.useRealTimers();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('execute (no distributed lock provider)', () => {
|
|
25
|
+
it('runs the callback and marks the key completed', async () => {
|
|
26
|
+
const service = makeService(noLockPrisma());
|
|
27
|
+
const callback = jest.fn().mockResolvedValue('ok');
|
|
28
|
+
|
|
29
|
+
await expect(service.execute('key-a', callback)).resolves.toBe('ok');
|
|
30
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
31
|
+
expect((service as any).isCompleted('key-a')).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('rejects a second call with the same key as already processed', async () => {
|
|
35
|
+
const service = makeService(noLockPrisma());
|
|
36
|
+
await service.execute('key-b', jest.fn().mockResolvedValue(1));
|
|
37
|
+
|
|
38
|
+
await expect(
|
|
39
|
+
service.execute('key-b', jest.fn().mockResolvedValue(2)),
|
|
40
|
+
).rejects.toThrow(OAuthCallbackAlreadyProcessedError);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('does not mark completed when the callback throws a generic error', async () => {
|
|
44
|
+
const service = makeService(noLockPrisma());
|
|
45
|
+
await expect(
|
|
46
|
+
service.execute('key-c', jest.fn().mockRejectedValue(new Error('boom'))),
|
|
47
|
+
).rejects.toThrow('boom');
|
|
48
|
+
expect((service as any).isCompleted('key-c')).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('marks completed when the callback throws a callback_consumed provider error', async () => {
|
|
52
|
+
const service = makeService(noLockPrisma());
|
|
53
|
+
const err = new OAuthProviderException('google', 'callback_consumed', 'consumed');
|
|
54
|
+
await expect(service.execute('key-d', jest.fn().mockRejectedValue(err))).rejects.toBe(err);
|
|
55
|
+
expect((service as any).isCompleted('key-d')).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('execute (postgres advisory lock)', () => {
|
|
60
|
+
it('acquires the advisory lock and runs the callback', async () => {
|
|
61
|
+
const tx = { $queryRaw: jest.fn().mockResolvedValue([{ locked: true }]) };
|
|
62
|
+
const prisma = {
|
|
63
|
+
isPostgres: jest.fn().mockReturnValue(true),
|
|
64
|
+
isMysql: jest.fn().mockReturnValue(false),
|
|
65
|
+
getProvider: jest.fn().mockReturnValue('postgresql'),
|
|
66
|
+
$transaction: jest.fn(async (cb: any) => cb(tx)),
|
|
67
|
+
};
|
|
68
|
+
const service = makeService(prisma);
|
|
69
|
+
const callback = jest.fn().mockResolvedValue('done');
|
|
70
|
+
|
|
71
|
+
await expect(service.execute('pg-key', callback)).resolves.toBe('done');
|
|
72
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
73
|
+
expect((service as any).isCompleted('pg-key')).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('throws InProgress and does not run the callback when the lock is held', async () => {
|
|
77
|
+
const tx = { $queryRaw: jest.fn().mockResolvedValue([{ locked: false }]) };
|
|
78
|
+
const prisma = {
|
|
79
|
+
isPostgres: jest.fn().mockReturnValue(true),
|
|
80
|
+
isMysql: jest.fn().mockReturnValue(false),
|
|
81
|
+
getProvider: jest.fn().mockReturnValue('postgresql'),
|
|
82
|
+
$transaction: jest.fn(async (cb: any) => cb(tx)),
|
|
83
|
+
};
|
|
84
|
+
const service = makeService(prisma);
|
|
85
|
+
const callback = jest.fn();
|
|
86
|
+
|
|
87
|
+
await expect(service.execute('pg-busy', callback)).rejects.toThrow(OAuthCallbackInProgressError);
|
|
88
|
+
expect(callback).not.toHaveBeenCalled();
|
|
89
|
+
expect((service as any).isCompleted('pg-busy')).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('completed-callback bookkeeping', () => {
|
|
94
|
+
it('expires completed entries after the TTL window', () => {
|
|
95
|
+
jest.useFakeTimers();
|
|
96
|
+
jest.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
|
|
97
|
+
const service = makeService(noLockPrisma());
|
|
98
|
+
|
|
99
|
+
(service as any).markCompleted('ttl-key');
|
|
100
|
+
expect((service as any).isCompleted('ttl-key')).toBe(true);
|
|
101
|
+
|
|
102
|
+
// Advance beyond the 10 minute TTL.
|
|
103
|
+
jest.setSystemTime(new Date('2026-01-01T00:11:00.000Z'));
|
|
104
|
+
expect((service as any).isCompleted('ttl-key')).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('prunes expired entries from the internal map', () => {
|
|
108
|
+
jest.useFakeTimers();
|
|
109
|
+
jest.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
|
|
110
|
+
const service = makeService(noLockPrisma());
|
|
111
|
+
(service as any).markCompleted('old');
|
|
112
|
+
|
|
113
|
+
jest.setSystemTime(new Date('2026-01-01T00:11:00.000Z'));
|
|
114
|
+
(service as any).markCompleted('fresh');
|
|
115
|
+
(service as any).pruneCompletedCallbacks();
|
|
116
|
+
|
|
117
|
+
expect((service as any).completedCallbacks.has('old')).toBe(false);
|
|
118
|
+
expect((service as any).completedCallbacks.has('fresh')).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('shouldMarkCompleted', () => {
|
|
123
|
+
it('is true for already-processed and callback_consumed errors only', () => {
|
|
124
|
+
const service = makeService(noLockPrisma());
|
|
125
|
+
expect((service as any).shouldMarkCompleted(new OAuthCallbackAlreadyProcessedError())).toBe(true);
|
|
126
|
+
expect(
|
|
127
|
+
(service as any).shouldMarkCompleted(
|
|
128
|
+
new OAuthProviderException('g', 'callback_consumed', 'x'),
|
|
129
|
+
),
|
|
130
|
+
).toBe(true);
|
|
131
|
+
expect(
|
|
132
|
+
(service as any).shouldMarkCompleted(
|
|
133
|
+
new OAuthProviderException('g', 'upstream_failure', 'x'),
|
|
134
|
+
),
|
|
135
|
+
).toBe(false);
|
|
136
|
+
expect((service as any).shouldMarkCompleted(new Error('other'))).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('lock key derivation', () => {
|
|
141
|
+
it('derives a deterministic signed bigint within int64 bounds', () => {
|
|
142
|
+
const service = makeService(noLockPrisma());
|
|
143
|
+
const a = (service as any).toSignedBigInt('same');
|
|
144
|
+
const b = (service as any).toSignedBigInt('same');
|
|
145
|
+
expect(a).toBe(b);
|
|
146
|
+
expect(typeof a).toBe('bigint');
|
|
147
|
+
expect(a >= -(BigInt(1) << BigInt(63))).toBe(true);
|
|
148
|
+
expect(a < BigInt(1) << BigInt(63)).toBe(true);
|
|
149
|
+
expect((service as any).toSignedBigInt('other')).not.toBe(a);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('derives a prefixed 48-char mysql lock name', () => {
|
|
153
|
+
const service = makeService(noLockPrisma());
|
|
154
|
+
const name = (service as any).toMysqlLockName('key');
|
|
155
|
+
expect(name.startsWith('oauth:')).toBe(true);
|
|
156
|
+
expect(name.slice('oauth:'.length)).toHaveLength(48);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -26,11 +26,11 @@ export class FacebookProvider extends BaseOAuthProvider {
|
|
|
26
26
|
'oauth-facebook-profile-id',
|
|
27
27
|
'url',
|
|
28
28
|
]);
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
29
|
+
const profileSlug = String(settings['oauth-facebook-profile-id'] ?? '').trim();
|
|
30
|
+
if (!profileSlug) throw new BadRequestException('Facebook OAuth profile not configured.');
|
|
31
31
|
|
|
32
32
|
const profile = await this.prisma.integration_profile.findUnique({
|
|
33
|
-
where: {
|
|
33
|
+
where: { slug: profileSlug },
|
|
34
34
|
include: { integration_provider: { select: { slug: true } } },
|
|
35
35
|
});
|
|
36
36
|
if (!profile) throw new BadRequestException('Facebook OAuth profile not found.');
|
|
@@ -30,11 +30,11 @@ export class GithubProvider extends BaseOAuthProvider {
|
|
|
30
30
|
'oauth-github-profile-id',
|
|
31
31
|
'api-url',
|
|
32
32
|
]);
|
|
33
|
-
const
|
|
34
|
-
if (!
|
|
33
|
+
const profileSlug = String(settings['oauth-github-profile-id'] ?? '').trim();
|
|
34
|
+
if (!profileSlug) throw new BadRequestException('GitHub OAuth profile not configured.');
|
|
35
35
|
|
|
36
36
|
const profile = await this.prisma.integration_profile.findUnique({
|
|
37
|
-
where: {
|
|
37
|
+
where: { slug: profileSlug },
|
|
38
38
|
include: { integration_provider: { select: { slug: true } } },
|
|
39
39
|
});
|
|
40
40
|
if (!profile) throw new BadRequestException('GitHub OAuth profile not found.');
|
|
@@ -26,11 +26,11 @@ export class GoogleProvider extends BaseOAuthProvider {
|
|
|
26
26
|
'oauth-google-profile-id',
|
|
27
27
|
'url',
|
|
28
28
|
]);
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
29
|
+
const profileSlug = String(settings['oauth-google-profile-id'] ?? '').trim();
|
|
30
|
+
if (!profileSlug) throw new BadRequestException('Google OAuth profile not configured.');
|
|
31
31
|
|
|
32
32
|
const profile = await this.prisma.integration_profile.findUnique({
|
|
33
|
-
where: {
|
|
33
|
+
where: { slug: profileSlug },
|
|
34
34
|
include: { integration_provider: { select: { slug: true } } },
|
|
35
35
|
});
|
|
36
36
|
if (!profile) throw new BadRequestException('Google OAuth profile not found.');
|
|
@@ -40,11 +40,11 @@ export class MicrosoftEntraIdProvider extends BaseOAuthProvider {
|
|
|
40
40
|
'oauth-microsoft-entra-id-profile-id',
|
|
41
41
|
'url',
|
|
42
42
|
]);
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
43
|
+
const profileSlug = String(settings['oauth-microsoft-entra-id-profile-id'] ?? '').trim();
|
|
44
|
+
if (!profileSlug) throw new BadRequestException('Microsoft Entra ID OAuth profile not configured.');
|
|
45
45
|
|
|
46
46
|
const profile = await this.prisma.integration_profile.findUnique({
|
|
47
|
-
where: {
|
|
47
|
+
where: { slug: profileSlug },
|
|
48
48
|
include: { integration_provider: { select: { slug: true } } },
|
|
49
49
|
});
|
|
50
50
|
if (!profile) throw new BadRequestException('Microsoft Entra ID OAuth profile not found.');
|
|
@@ -26,11 +26,11 @@ export class MicrosoftProvider extends BaseOAuthProvider {
|
|
|
26
26
|
'oauth-microsoft-profile-id',
|
|
27
27
|
'url',
|
|
28
28
|
]);
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
29
|
+
const profileSlug = String(settings['oauth-microsoft-profile-id'] ?? '').trim();
|
|
30
|
+
if (!profileSlug) throw new BadRequestException('Microsoft OAuth profile not configured.');
|
|
31
31
|
|
|
32
32
|
const profile = await this.prisma.integration_profile.findUnique({
|
|
33
|
-
where: {
|
|
33
|
+
where: { slug: profileSlug },
|
|
34
34
|
include: { integration_provider: { select: { slug: true } } },
|
|
35
35
|
});
|
|
36
36
|
if (!profile) throw new BadRequestException('Microsoft OAuth profile not found.');
|
package/src/pdf/pdf.service.ts
CHANGED
|
@@ -15,6 +15,10 @@ export interface PdfGenerateOptions {
|
|
|
15
15
|
margin?: { top?: string; right?: string; bottom?: string; left?: string };
|
|
16
16
|
printBackground?: boolean;
|
|
17
17
|
landscape?: boolean;
|
|
18
|
+
/** Explicit CSS page size (e.g. '1600px'). Takes precedence over `format`/`landscape` when set. */
|
|
19
|
+
width?: string;
|
|
20
|
+
/** Explicit CSS page size (e.g. '1131px'). Takes precedence over `format`/`landscape` when set. */
|
|
21
|
+
height?: string;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -161,12 +165,21 @@ export class PdfService {
|
|
|
161
165
|
await page.setViewportSize({ width: 794, height: 1123 });
|
|
162
166
|
await page.setContent(html, { waitUntil: 'networkidle' });
|
|
163
167
|
|
|
164
|
-
const pdfBuffer = await page.pdf(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
const pdfBuffer = await page.pdf(
|
|
169
|
+
options.width && options.height
|
|
170
|
+
? {
|
|
171
|
+
width: options.width,
|
|
172
|
+
height: options.height,
|
|
173
|
+
printBackground: options.printBackground ?? true,
|
|
174
|
+
margin: options.margin ?? { top: '0', right: '0', bottom: '0', left: '0' },
|
|
175
|
+
}
|
|
176
|
+
: {
|
|
177
|
+
format: options.format ?? 'A4',
|
|
178
|
+
printBackground: options.printBackground ?? true,
|
|
179
|
+
landscape: options.landscape ?? false,
|
|
180
|
+
margin: options.margin ?? { top: '0', right: '0', bottom: '0', left: '0' },
|
|
181
|
+
},
|
|
182
|
+
);
|
|
170
183
|
|
|
171
184
|
return Buffer.from(pdfBuffer);
|
|
172
185
|
} catch (error) {
|
|
@@ -44,9 +44,10 @@ export class ProfileController {
|
|
|
44
44
|
async verifyBeforeAddMfa(
|
|
45
45
|
@User() { id },
|
|
46
46
|
@Body() data: VerifyBeforeAddMfaDto,
|
|
47
|
-
@Locale() locale: string
|
|
47
|
+
@Locale() locale: string,
|
|
48
|
+
@Headers('origin') origin: string
|
|
48
49
|
) {
|
|
49
|
-
return this.profileService.verifyBeforeAddMfa(locale, id, data);
|
|
50
|
+
return this.profileService.verifyBeforeAddMfa(locale, id, data, origin);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
@Post('totp/generate')
|
|
@@ -130,9 +131,10 @@ export class ProfileController {
|
|
|
130
131
|
@User() { id },
|
|
131
132
|
@Param('mfaId', ParseIntPipe) mfaId: number,
|
|
132
133
|
@Body() data: RemoveMfaDto,
|
|
133
|
-
@Locale() locale: string
|
|
134
|
+
@Locale() locale: string,
|
|
135
|
+
@Headers('origin') origin: string
|
|
134
136
|
) {
|
|
135
|
-
return this.profileService.removeMfa(locale, id, mfaId, data);
|
|
137
|
+
return this.profileService.removeMfa(locale, id, mfaId, data, origin);
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
@Post('recovery-codes/send-verification')
|
|
@@ -147,43 +149,48 @@ export class ProfileController {
|
|
|
147
149
|
async regenerateRecoveryCodes(
|
|
148
150
|
@User() { id },
|
|
149
151
|
@Body() data: RegenerateRecoveryCodesDto,
|
|
150
|
-
@Locale() locale: string
|
|
152
|
+
@Locale() locale: string,
|
|
153
|
+
@Headers('origin') origin: string
|
|
151
154
|
) {
|
|
152
|
-
return this.profileService.regenerateRecoveryCodes(locale, id, data);
|
|
155
|
+
return this.profileService.regenerateRecoveryCodes(locale, id, data, origin);
|
|
153
156
|
}
|
|
154
157
|
|
|
155
158
|
@Post('webauthn/generate')
|
|
156
159
|
async generateWebAuthnRegistrationOptions(
|
|
157
160
|
@User() { id },
|
|
158
161
|
@Body() { name }: GenerateWebAuthnRegistrationOptionsDto,
|
|
159
|
-
@Locale() locale: string
|
|
162
|
+
@Locale() locale: string,
|
|
163
|
+
@Headers('origin') origin: string
|
|
160
164
|
) {
|
|
161
|
-
return this.profileService.generateWebAuthnRegistrationOptions(locale, id, name);
|
|
165
|
+
return this.profileService.generateWebAuthnRegistrationOptions(locale, id, name, origin);
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
@Post('webauthn/verify')
|
|
165
169
|
async verifyWebAuthnRegistration(
|
|
166
170
|
@User() { id },
|
|
167
171
|
@Body() { name, attestationResponse }: VerifyWebAuthnRegistrationDto,
|
|
168
|
-
@Locale() locale: string
|
|
172
|
+
@Locale() locale: string,
|
|
173
|
+
@Headers('origin') origin: string
|
|
169
174
|
) {
|
|
170
|
-
return this.profileService.verifyWebAuthnRegistration(locale, id, name, attestationResponse);
|
|
175
|
+
return this.profileService.verifyWebAuthnRegistration(locale, id, name, attestationResponse, origin);
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
@Post('webauthn/authenticate/generate')
|
|
174
179
|
async generateWebAuthnAuthenticationOptions(
|
|
175
180
|
@User() { id },
|
|
176
|
-
@Locale() locale: string
|
|
181
|
+
@Locale() locale: string,
|
|
182
|
+
@Headers('origin') origin: string
|
|
177
183
|
) {
|
|
178
|
-
return this.profileService.generateWebAuthnAuthenticationOptions(locale, id);
|
|
184
|
+
return this.profileService.generateWebAuthnAuthenticationOptions(locale, id, origin);
|
|
179
185
|
}
|
|
180
186
|
|
|
181
187
|
@Post('webauthn/authenticate/verify')
|
|
182
188
|
async verifyWebAuthnAuthentication(
|
|
183
189
|
@User() { id },
|
|
184
|
-
@Body() { assertionResponse }: VerifyWebAuthnDto
|
|
190
|
+
@Body() { assertionResponse }: VerifyWebAuthnDto,
|
|
191
|
+
@Headers('origin') origin: string
|
|
185
192
|
) {
|
|
186
|
-
return this.profileService.verifyWebAuthnAuthentication(id, assertionResponse);
|
|
193
|
+
return this.profileService.verifyWebAuthnAuthentication(id, assertionResponse, origin);
|
|
187
194
|
}
|
|
188
195
|
|
|
189
196
|
@Delete('webauthn/:mfaId/remove')
|
|
@@ -22,6 +22,7 @@ import { SecurityService } from '../security/security.service';
|
|
|
22
22
|
import { SettingService } from '../setting/setting.service';
|
|
23
23
|
import { TokenService } from '../token/token.service';
|
|
24
24
|
import { UserService } from '../user/user.service';
|
|
25
|
+
import { resolveWebAuthnOrigin } from '../utils/webauthn-origin';
|
|
25
26
|
import { ChangeEmailDto } from './dto/change-email.dto';
|
|
26
27
|
import { ChangePasswordDto } from './dto/change-password.dto';
|
|
27
28
|
import { EmailVerificationDto } from './dto/email-verification-confirm.dto';
|
|
@@ -294,6 +295,8 @@ export class ProfileService {
|
|
|
294
295
|
this.eventEmitter.emit(PROFILE_EMAIL_CHANGED, new ProfileEmailChangedEvent(userId, confirmedIdentifier.value));
|
|
295
296
|
}
|
|
296
297
|
|
|
298
|
+
await this.user.registerUserActivity(userId, 'changeEmail');
|
|
299
|
+
|
|
297
300
|
return { success: true };
|
|
298
301
|
}
|
|
299
302
|
|
|
@@ -349,6 +352,7 @@ export class ProfileService {
|
|
|
349
352
|
});
|
|
350
353
|
if (name) {
|
|
351
354
|
this.eventEmitter.emit(PROFILE_UPDATED, new ProfileUpdatedEvent(userid, name));
|
|
355
|
+
await this.user.registerUserActivity(userid, 'updateProfile');
|
|
352
356
|
}
|
|
353
357
|
return result;
|
|
354
358
|
}
|
|
@@ -356,6 +360,7 @@ export class ProfileService {
|
|
|
356
360
|
async updateAvatar(locale: string, userId: number, file: MulterFile) {
|
|
357
361
|
const result = await this.user.changeAvatar(locale, userId, file);
|
|
358
362
|
this.eventEmitter.emit(PROFILE_AVATAR_UPDATED, new ProfileAvatarUpdatedEvent(userId, result?.id ?? null));
|
|
363
|
+
await this.user.registerUserActivity(userId, 'avatar_changed');
|
|
359
364
|
return result;
|
|
360
365
|
}
|
|
361
366
|
|
|
@@ -405,6 +410,8 @@ export class ProfileService {
|
|
|
405
410
|
});
|
|
406
411
|
}
|
|
407
412
|
|
|
413
|
+
await this.user.registerUserActivity(userId, 'changePassword');
|
|
414
|
+
|
|
408
415
|
return { success: true };
|
|
409
416
|
}
|
|
410
417
|
|
|
@@ -656,7 +663,7 @@ export class ProfileService {
|
|
|
656
663
|
}
|
|
657
664
|
}
|
|
658
665
|
|
|
659
|
-
async removeMfa(locale: string, userId: number, mfaId: number, data: { token?: string; hash?: string; verificationType?: 'totp' | 'email' | 'recovery' | 'webauthn'; assertionResponse?: any }) {
|
|
666
|
+
async removeMfa(locale: string, userId: number, mfaId: number, data: { token?: string; hash?: string; verificationType?: 'totp' | 'email' | 'recovery' | 'webauthn'; assertionResponse?: any }, requestOrigin?: string) {
|
|
660
667
|
try {
|
|
661
668
|
const user = await this.prisma.user.findUnique({
|
|
662
669
|
where: { id: userId },
|
|
@@ -700,7 +707,7 @@ export class ProfileService {
|
|
|
700
707
|
}
|
|
701
708
|
|
|
702
709
|
try {
|
|
703
|
-
await this.verifyWebAuthnAuthentication(userId, data.assertionResponse);
|
|
710
|
+
await this.verifyWebAuthnAuthentication(userId, data.assertionResponse, requestOrigin);
|
|
704
711
|
} catch (error) {
|
|
705
712
|
throw new BadRequestException(
|
|
706
713
|
getLocaleText('invalidWebAuthn', locale, 'WebAuthn verification failed.')
|
|
@@ -899,7 +906,8 @@ export class ProfileService {
|
|
|
899
906
|
async verifyBeforeAddMfa(
|
|
900
907
|
locale: string,
|
|
901
908
|
userId: number,
|
|
902
|
-
data: { verificationCode?: string; hash?: string; verificationType: 'totp' | 'email' | 'recovery' | 'webauthn'; assertionResponse?: any }
|
|
909
|
+
data: { verificationCode?: string; hash?: string; verificationType: 'totp' | 'email' | 'recovery' | 'webauthn'; assertionResponse?: any },
|
|
910
|
+
requestOrigin?: string
|
|
903
911
|
) {
|
|
904
912
|
try {
|
|
905
913
|
const user = await this.prisma.user.findUnique({
|
|
@@ -935,7 +943,7 @@ export class ProfileService {
|
|
|
935
943
|
}
|
|
936
944
|
|
|
937
945
|
try {
|
|
938
|
-
await this.verifyWebAuthnAuthentication(userId, data.assertionResponse);
|
|
946
|
+
await this.verifyWebAuthnAuthentication(userId, data.assertionResponse, requestOrigin);
|
|
939
947
|
} catch (error) {
|
|
940
948
|
throw new BadRequestException(
|
|
941
949
|
getLocaleText('invalidWebAuthn', locale, 'WebAuthn verification failed.')
|
|
@@ -1087,7 +1095,7 @@ export class ProfileService {
|
|
|
1087
1095
|
}
|
|
1088
1096
|
}
|
|
1089
1097
|
|
|
1090
|
-
async regenerateRecoveryCodes(locale: string, userId: number, data: { verificationCode?: string; hash?: string; verificationType?: 'totp' | 'email' | 'recovery' | 'webauthn'; assertionResponse?: any }) {
|
|
1098
|
+
async regenerateRecoveryCodes(locale: string, userId: number, data: { verificationCode?: string; hash?: string; verificationType?: 'totp' | 'email' | 'recovery' | 'webauthn'; assertionResponse?: any }, requestOrigin?: string) {
|
|
1091
1099
|
try {
|
|
1092
1100
|
const user = await this.prisma.user.findUnique({
|
|
1093
1101
|
where: { id: userId },
|
|
@@ -1125,7 +1133,7 @@ export class ProfileService {
|
|
|
1125
1133
|
}
|
|
1126
1134
|
|
|
1127
1135
|
try {
|
|
1128
|
-
await this.verifyWebAuthnAuthentication(userId, data.assertionResponse);
|
|
1136
|
+
await this.verifyWebAuthnAuthentication(userId, data.assertionResponse, requestOrigin);
|
|
1129
1137
|
} catch (error) {
|
|
1130
1138
|
throw new BadRequestException(
|
|
1131
1139
|
getLocaleText('invalidWebAuthn', locale, 'WebAuthn verification failed.')
|
|
@@ -1213,7 +1221,7 @@ export class ProfileService {
|
|
|
1213
1221
|
}
|
|
1214
1222
|
}
|
|
1215
1223
|
|
|
1216
|
-
async generateWebAuthnRegistrationOptions(locale: string, userId: number, name: string) {
|
|
1224
|
+
async generateWebAuthnRegistrationOptions(locale: string, userId: number, name: string, requestOrigin?: string) {
|
|
1217
1225
|
try {
|
|
1218
1226
|
const user = await this.prisma.user.findFirst({
|
|
1219
1227
|
where: { id: userId },
|
|
@@ -1244,8 +1252,7 @@ export class ProfileService {
|
|
|
1244
1252
|
]);
|
|
1245
1253
|
|
|
1246
1254
|
const rpName = settings['system-name'] || 'HedHog';
|
|
1247
|
-
const
|
|
1248
|
-
const rpID = new URL(primaryOrigin).hostname;
|
|
1255
|
+
const { rpID } = resolveWebAuthnOrigin(requestOrigin);
|
|
1249
1256
|
const userEmail = user.user_identifier.find(ui => ui.type === 'email')?.value || `user${userId}@example.com`;
|
|
1250
1257
|
const existingAuthenticators = user.user_mfa
|
|
1251
1258
|
.filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
|
|
@@ -1295,7 +1302,8 @@ export class ProfileService {
|
|
|
1295
1302
|
locale: string,
|
|
1296
1303
|
userId: number,
|
|
1297
1304
|
name: string,
|
|
1298
|
-
attestationResponse: any
|
|
1305
|
+
attestationResponse: any,
|
|
1306
|
+
requestOrigin?: string
|
|
1299
1307
|
) {
|
|
1300
1308
|
try {
|
|
1301
1309
|
const challengeRecord = await this.prisma.user_mfa_challenge.findFirst({
|
|
@@ -1317,9 +1325,7 @@ export class ProfileService {
|
|
|
1317
1325
|
);
|
|
1318
1326
|
}
|
|
1319
1327
|
|
|
1320
|
-
const
|
|
1321
|
-
const rpID = new URL(primaryOrigin).hostname;
|
|
1322
|
-
const expectedOrigin = primaryOrigin;
|
|
1328
|
+
const { rpID, expectedOrigin } = resolveWebAuthnOrigin(requestOrigin);
|
|
1323
1329
|
const verification: VerifiedRegistrationResponse = await verifyRegistrationResponse({
|
|
1324
1330
|
response: attestationResponse,
|
|
1325
1331
|
expectedChallenge: challengeRecord.hash,
|
|
@@ -1361,7 +1367,7 @@ export class ProfileService {
|
|
|
1361
1367
|
}
|
|
1362
1368
|
}
|
|
1363
1369
|
|
|
1364
|
-
async generateWebAuthnAuthenticationOptions(locale: string, userId: number) {
|
|
1370
|
+
async generateWebAuthnAuthenticationOptions(locale: string, userId: number, requestOrigin?: string) {
|
|
1365
1371
|
try {
|
|
1366
1372
|
const user = await this.prisma.user.findFirst({
|
|
1367
1373
|
where: { id: userId },
|
|
@@ -1379,8 +1385,7 @@ export class ProfileService {
|
|
|
1379
1385
|
);
|
|
1380
1386
|
}
|
|
1381
1387
|
|
|
1382
|
-
const
|
|
1383
|
-
const rpID = new URL(primaryOrigin).hostname;
|
|
1388
|
+
const { rpID } = resolveWebAuthnOrigin(requestOrigin);
|
|
1384
1389
|
const allowCredentials = user.user_mfa
|
|
1385
1390
|
.filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
|
|
1386
1391
|
.map(mfa => ({
|
|
@@ -1408,7 +1413,7 @@ export class ProfileService {
|
|
|
1408
1413
|
}
|
|
1409
1414
|
}
|
|
1410
1415
|
|
|
1411
|
-
async verifyWebAuthnAuthentication(userId: number, assertionResponse: any) {
|
|
1416
|
+
async verifyWebAuthnAuthentication(userId: number, assertionResponse: any, requestOrigin?: string) {
|
|
1412
1417
|
try {
|
|
1413
1418
|
const challengeRecord = await this.prisma.user_mfa_challenge.findFirst({
|
|
1414
1419
|
where: {
|
|
@@ -1433,9 +1438,7 @@ export class ProfileService {
|
|
|
1433
1438
|
}
|
|
1434
1439
|
|
|
1435
1440
|
const authenticator = challengeRecord.user_mfa.user_mfa_webauthn[0];
|
|
1436
|
-
const
|
|
1437
|
-
const rpID = new URL(primaryOrigin).hostname;
|
|
1438
|
-
const expectedOrigin = primaryOrigin;
|
|
1441
|
+
const { rpID, expectedOrigin } = resolveWebAuthnOrigin(requestOrigin);
|
|
1439
1442
|
const verification: VerifiedAuthenticationResponse = await verifyAuthenticationResponse({
|
|
1440
1443
|
response: assertionResponse,
|
|
1441
1444
|
expectedChallenge: challengeRecord.hash,
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { getLocaleText } from '@hed-hog/api-locale';
|
|
2
|
-
import { IsIn, IsOptional, IsString } from 'class-validator';
|
|
3
|
-
import { HttpMethod } from '../../types/http-method';
|
|
4
|
-
|
|
5
|
-
export type RouteType = 'HTTP' | 'MCP';
|
|
6
|
-
|
|
7
|
-
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
|
|
8
|
-
|
|
9
|
-
export class CreateDTO {
|
|
10
|
-
@IsOptional()
|
|
11
|
-
@IsString({ message: (args) => getLocaleText('validation.urlMustBeString', args.value) })
|
|
12
|
-
url?: string;
|
|
13
|
-
|
|
14
|
-
@IsOptional()
|
|
15
|
-
@IsString({ message: (args) => getLocaleText('validation.methodMustBeString', args.value) })
|
|
16
|
-
@IsIn(HTTP_METHODS, {
|
|
17
|
-
message: (args) => getLocaleText('validation.methodMustBeValid', args.value),
|
|
18
|
-
})
|
|
19
|
-
method?: HttpMethod;
|
|
20
|
-
|
|
21
|
-
@IsOptional()
|
|
22
|
-
@IsIn(['HTTP', 'MCP'])
|
|
23
|
-
type?: RouteType;
|
|
24
|
-
|
|
25
|
-
@IsOptional()
|
|
26
|
-
@IsString()
|
|
27
|
-
tool_name?: string;
|
|
28
|
-
|
|
29
|
-
@IsOptional()
|
|
30
|
-
@IsString()
|
|
31
|
-
name?: string;
|
|
32
|
-
}
|
|
1
|
+
import { getLocaleText } from '@hed-hog/api-locale';
|
|
2
|
+
import { IsIn, IsOptional, IsString } from 'class-validator';
|
|
3
|
+
import { HttpMethod } from '../../types/http-method';
|
|
4
|
+
|
|
5
|
+
export type RouteType = 'HTTP' | 'MCP';
|
|
6
|
+
|
|
7
|
+
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
|
|
8
|
+
|
|
9
|
+
export class CreateDTO {
|
|
10
|
+
@IsOptional()
|
|
11
|
+
@IsString({ message: (args) => getLocaleText('validation.urlMustBeString', args.value) })
|
|
12
|
+
url?: string;
|
|
13
|
+
|
|
14
|
+
@IsOptional()
|
|
15
|
+
@IsString({ message: (args) => getLocaleText('validation.methodMustBeString', args.value) })
|
|
16
|
+
@IsIn(HTTP_METHODS, {
|
|
17
|
+
message: (args) => getLocaleText('validation.methodMustBeValid', args.value),
|
|
18
|
+
})
|
|
19
|
+
method?: HttpMethod;
|
|
20
|
+
|
|
21
|
+
@IsOptional()
|
|
22
|
+
@IsIn(['HTTP', 'MCP'])
|
|
23
|
+
type?: RouteType;
|
|
24
|
+
|
|
25
|
+
@IsOptional()
|
|
26
|
+
@IsString()
|
|
27
|
+
tool_name?: string;
|
|
28
|
+
|
|
29
|
+
@IsOptional()
|
|
30
|
+
@IsString()
|
|
31
|
+
name?: string;
|
|
32
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { getLocaleText } from '@hed-hog/api-locale';
|
|
2
|
-
import { IsIn, IsOptional, IsString } from 'class-validator';
|
|
3
|
-
import { HttpMethod } from '../../types/http-method';
|
|
4
|
-
import { RouteType } from './create.dto';
|
|
5
|
-
|
|
6
|
-
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
|
|
7
|
-
|
|
8
|
-
export class UpdateDTO {
|
|
9
|
-
@IsOptional()
|
|
10
|
-
@IsString({ message: (args) => getLocaleText('validation.urlMustBeString', args.value) })
|
|
11
|
-
url?: string;
|
|
12
|
-
|
|
13
|
-
@IsOptional()
|
|
14
|
-
@IsString({ message: (args) => getLocaleText('validation.methodMustBeString', args.value) })
|
|
15
|
-
@IsIn(HTTP_METHODS, {
|
|
16
|
-
message: (args) => getLocaleText('validation.methodMustBeValid', args.value),
|
|
17
|
-
})
|
|
18
|
-
method?: HttpMethod;
|
|
19
|
-
|
|
20
|
-
@IsOptional()
|
|
21
|
-
@IsIn(['HTTP', 'MCP'])
|
|
22
|
-
type?: RouteType;
|
|
23
|
-
|
|
24
|
-
@IsOptional()
|
|
25
|
-
@IsString()
|
|
26
|
-
tool_name?: string;
|
|
27
|
-
|
|
28
|
-
@IsOptional()
|
|
29
|
-
@IsString()
|
|
30
|
-
name?: string;
|
|
31
|
-
}
|
|
1
|
+
import { getLocaleText } from '@hed-hog/api-locale';
|
|
2
|
+
import { IsIn, IsOptional, IsString } from 'class-validator';
|
|
3
|
+
import { HttpMethod } from '../../types/http-method';
|
|
4
|
+
import { RouteType } from './create.dto';
|
|
5
|
+
|
|
6
|
+
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
|
|
7
|
+
|
|
8
|
+
export class UpdateDTO {
|
|
9
|
+
@IsOptional()
|
|
10
|
+
@IsString({ message: (args) => getLocaleText('validation.urlMustBeString', args.value) })
|
|
11
|
+
url?: string;
|
|
12
|
+
|
|
13
|
+
@IsOptional()
|
|
14
|
+
@IsString({ message: (args) => getLocaleText('validation.methodMustBeString', args.value) })
|
|
15
|
+
@IsIn(HTTP_METHODS, {
|
|
16
|
+
message: (args) => getLocaleText('validation.methodMustBeValid', args.value),
|
|
17
|
+
})
|
|
18
|
+
method?: HttpMethod;
|
|
19
|
+
|
|
20
|
+
@IsOptional()
|
|
21
|
+
@IsIn(['HTTP', 'MCP'])
|
|
22
|
+
type?: RouteType;
|
|
23
|
+
|
|
24
|
+
@IsOptional()
|
|
25
|
+
@IsString()
|
|
26
|
+
tool_name?: string;
|
|
27
|
+
|
|
28
|
+
@IsOptional()
|
|
29
|
+
@IsString()
|
|
30
|
+
name?: string;
|
|
31
|
+
}
|