@kelpie/server 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +2 -0
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/actor.d.ts +3 -1
- package/dist/lib/actor.d.ts.map +1 -1
- package/dist/lib/actor.js.map +1 -1
- package/dist/lib/apiKeyScopes.d.ts +8 -0
- package/dist/lib/apiKeyScopes.d.ts.map +1 -0
- package/dist/lib/apiKeyScopes.js +166 -0
- package/dist/lib/apiKeyScopes.js.map +1 -0
- package/dist/lib/errors.d.ts +18 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +17 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/modules/activities/repository.d.ts +2 -0
- package/dist/modules/activities/repository.d.ts.map +1 -1
- package/dist/modules/activities/repository.js +9 -0
- package/dist/modules/activities/repository.js.map +1 -1
- package/dist/modules/agent-tasks/repository.d.ts +2 -0
- package/dist/modules/agent-tasks/repository.d.ts.map +1 -1
- package/dist/modules/agent-tasks/repository.js +9 -0
- package/dist/modules/agent-tasks/repository.js.map +1 -1
- package/dist/modules/api-keys/routes.d.ts.map +1 -1
- package/dist/modules/api-keys/routes.js +4 -1
- package/dist/modules/api-keys/routes.js.map +1 -1
- package/dist/modules/api-keys/schema.d.ts +43 -0
- package/dist/modules/api-keys/schema.d.ts.map +1 -1
- package/dist/modules/api-keys/schema.js +2 -0
- package/dist/modules/api-keys/schema.js.map +1 -1
- package/dist/modules/api-keys/scopeMiddleware.d.ts +8 -0
- package/dist/modules/api-keys/scopeMiddleware.d.ts.map +1 -0
- package/dist/modules/api-keys/scopeMiddleware.js +21 -0
- package/dist/modules/api-keys/scopeMiddleware.js.map +1 -0
- package/dist/modules/api-keys/service.d.ts +3 -1
- package/dist/modules/api-keys/service.d.ts.map +1 -1
- package/dist/modules/api-keys/service.js +26 -1
- package/dist/modules/api-keys/service.js.map +1 -1
- package/dist/modules/auth/credentials.d.ts.map +1 -1
- package/dist/modules/auth/credentials.js +2 -0
- package/dist/modules/auth/credentials.js.map +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +21 -4
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/repository.d.ts +2 -1
- package/dist/modules/auth/repository.d.ts.map +1 -1
- package/dist/modules/auth/repository.js +1 -0
- package/dist/modules/auth/repository.js.map +1 -1
- package/dist/modules/auth/routes.d.ts.map +1 -1
- package/dist/modules/auth/routes.js +2 -6
- package/dist/modules/auth/routes.js.map +1 -1
- package/dist/modules/auth/schema.d.ts +18 -1
- package/dist/modules/auth/schema.d.ts.map +1 -1
- package/dist/modules/auth/schema.js +12 -1
- package/dist/modules/auth/schema.js.map +1 -1
- package/dist/modules/auth/service.d.ts +18 -0
- package/dist/modules/auth/service.d.ts.map +1 -1
- package/dist/modules/auth/service.js +95 -6
- package/dist/modules/auth/service.js.map +1 -1
- package/dist/modules/auth/session.d.ts +12 -0
- package/dist/modules/auth/session.d.ts.map +1 -1
- package/dist/modules/auth/session.js +12 -0
- package/dist/modules/auth/session.js.map +1 -1
- package/dist/modules/consent-purposes/index.d.ts +1 -1
- package/dist/modules/consent-purposes/index.js +1 -1
- package/dist/modules/consent-purposes/schema.d.ts +3 -3
- package/dist/modules/consent-purposes/schema.js +3 -3
- package/dist/modules/consent-purposes/service.d.ts +1 -1
- package/dist/modules/consent-purposes/service.d.ts.map +1 -1
- package/dist/modules/consent-purposes/service.js +7 -16
- package/dist/modules/consent-purposes/service.js.map +1 -1
- package/dist/modules/consent-purposes/starters.d.ts +1 -1
- package/dist/modules/consent-purposes/tools.js +1 -1
- package/dist/modules/consent-purposes/tools.js.map +1 -1
- package/dist/modules/conversions/clearPointers.d.ts +10 -0
- package/dist/modules/conversions/clearPointers.d.ts.map +1 -0
- package/dist/modules/conversions/clearPointers.js +40 -0
- package/dist/modules/conversions/clearPointers.js.map +1 -0
- package/dist/modules/conversions/factory.d.ts +5 -0
- package/dist/modules/conversions/factory.d.ts.map +1 -0
- package/dist/modules/conversions/factory.js +18 -0
- package/dist/modules/conversions/factory.js.map +1 -0
- package/dist/modules/conversions/fieldMap.d.ts +55 -0
- package/dist/modules/conversions/fieldMap.d.ts.map +1 -0
- package/dist/modules/conversions/fieldMap.js +398 -0
- package/dist/modules/conversions/fieldMap.js.map +1 -0
- package/dist/modules/conversions/index.d.ts +7 -0
- package/dist/modules/conversions/index.d.ts.map +1 -0
- package/dist/modules/conversions/index.js +6 -0
- package/dist/modules/conversions/index.js.map +1 -0
- package/dist/modules/conversions/mcp.d.ts +7 -0
- package/dist/modules/conversions/mcp.d.ts.map +1 -0
- package/dist/modules/conversions/mcp.js +47 -0
- package/dist/modules/conversions/mcp.js.map +1 -0
- package/dist/modules/conversions/repoint.d.ts +19 -0
- package/dist/modules/conversions/repoint.d.ts.map +1 -0
- package/dist/modules/conversions/repoint.js +51 -0
- package/dist/modules/conversions/repoint.js.map +1 -0
- package/dist/modules/conversions/routes.d.ts +26 -0
- package/dist/modules/conversions/routes.d.ts.map +1 -0
- package/dist/modules/conversions/routes.js +23 -0
- package/dist/modules/conversions/routes.js.map +1 -0
- package/dist/modules/conversions/service.d.ts +28 -0
- package/dist/modules/conversions/service.d.ts.map +1 -0
- package/dist/modules/conversions/service.js +170 -0
- package/dist/modules/conversions/service.js.map +1 -0
- package/dist/modules/conversions/wire.d.ts +4 -0
- package/dist/modules/conversions/wire.d.ts.map +1 -0
- package/dist/modules/conversions/wire.js +28 -0
- package/dist/modules/conversions/wire.js.map +1 -0
- package/dist/modules/deals/events.d.ts +9 -0
- package/dist/modules/deals/events.d.ts.map +1 -1
- package/dist/modules/deals/events.js +4 -0
- package/dist/modules/deals/events.js.map +1 -1
- package/dist/modules/deals/index.d.ts.map +1 -1
- package/dist/modules/deals/index.js +4 -2
- package/dist/modules/deals/index.js.map +1 -1
- package/dist/modules/deals/routes.d.ts +2 -0
- package/dist/modules/deals/routes.d.ts.map +1 -1
- package/dist/modules/deals/routes.js +8 -1
- package/dist/modules/deals/routes.js.map +1 -1
- package/dist/modules/deals/schema.d.ts +34 -0
- package/dist/modules/deals/schema.d.ts.map +1 -1
- package/dist/modules/deals/schema.js +7 -2
- package/dist/modules/deals/schema.js.map +1 -1
- package/dist/modules/deals/service.d.ts.map +1 -1
- package/dist/modules/deals/service.js +2 -0
- package/dist/modules/deals/service.js.map +1 -1
- package/dist/modules/deals/tools.d.ts +2 -1
- package/dist/modules/deals/tools.d.ts.map +1 -1
- package/dist/modules/deals/tools.js +3 -1
- package/dist/modules/deals/tools.js.map +1 -1
- package/dist/modules/decisions/repository.d.ts +2 -1
- package/dist/modules/decisions/repository.d.ts.map +1 -1
- package/dist/modules/decisions/repository.js +9 -1
- package/dist/modules/decisions/repository.js.map +1 -1
- package/dist/modules/enquiries/events.d.ts +6 -2
- package/dist/modules/enquiries/events.d.ts.map +1 -1
- package/dist/modules/enquiries/events.js +5 -1
- package/dist/modules/enquiries/events.js.map +1 -1
- package/dist/modules/enquiries/index.d.ts.map +1 -1
- package/dist/modules/enquiries/index.js +4 -2
- package/dist/modules/enquiries/index.js.map +1 -1
- package/dist/modules/enquiries/routes.d.ts +2 -0
- package/dist/modules/enquiries/routes.d.ts.map +1 -1
- package/dist/modules/enquiries/routes.js +9 -9
- package/dist/modules/enquiries/routes.js.map +1 -1
- package/dist/modules/enquiries/schema.d.ts +36 -1
- package/dist/modules/enquiries/schema.d.ts.map +1 -1
- package/dist/modules/enquiries/schema.js +9 -3
- package/dist/modules/enquiries/schema.js.map +1 -1
- package/dist/modules/enquiries/service.d.ts +0 -8
- package/dist/modules/enquiries/service.d.ts.map +1 -1
- package/dist/modules/enquiries/service.js +3 -95
- package/dist/modules/enquiries/service.js.map +1 -1
- package/dist/modules/enquiries/tools.d.ts +2 -1
- package/dist/modules/enquiries/tools.d.ts.map +1 -1
- package/dist/modules/enquiries/tools.js +4 -16
- package/dist/modules/enquiries/tools.js.map +1 -1
- package/dist/modules/forms/applyMappedFields.d.ts +20 -0
- package/dist/modules/forms/applyMappedFields.d.ts.map +1 -0
- package/dist/modules/forms/applyMappedFields.js +169 -0
- package/dist/modules/forms/applyMappedFields.js.map +1 -0
- package/dist/modules/forms/applySubmissionFields.d.ts +18 -0
- package/dist/modules/forms/applySubmissionFields.d.ts.map +1 -0
- package/dist/modules/forms/applySubmissionFields.js +230 -0
- package/dist/modules/forms/applySubmissionFields.js.map +1 -0
- package/dist/modules/forms/fields.d.ts +7 -2
- package/dist/modules/forms/fields.d.ts.map +1 -1
- package/dist/modules/forms/fields.js +17 -6
- package/dist/modules/forms/fields.js.map +1 -1
- package/dist/modules/forms/mapping.d.ts +8 -2
- package/dist/modules/forms/mapping.d.ts.map +1 -1
- package/dist/modules/forms/mapping.js +11 -0
- package/dist/modules/forms/mapping.js.map +1 -1
- package/dist/modules/forms/repository.d.ts +0 -5
- package/dist/modules/forms/repository.d.ts.map +1 -1
- package/dist/modules/forms/repository.js +0 -2
- package/dist/modules/forms/repository.js.map +1 -1
- package/dist/modules/forms/routes.d.ts +2 -30
- package/dist/modules/forms/routes.d.ts.map +1 -1
- package/dist/modules/forms/routes.js +2 -2
- package/dist/modules/forms/routes.js.map +1 -1
- package/dist/modules/forms/schema.d.ts.map +1 -1
- package/dist/modules/forms/schema.js +1 -2
- package/dist/modules/forms/schema.js.map +1 -1
- package/dist/modules/forms/service.d.ts.map +1 -1
- package/dist/modules/forms/service.js +18 -3
- package/dist/modules/forms/service.js.map +1 -1
- package/dist/modules/forms/starters.d.ts +31 -0
- package/dist/modules/forms/starters.d.ts.map +1 -0
- package/dist/modules/forms/starters.js +94 -0
- package/dist/modules/forms/starters.js.map +1 -0
- package/dist/modules/forms/submission.d.ts +1 -0
- package/dist/modules/forms/submission.d.ts.map +1 -1
- package/dist/modules/forms/submission.js +58 -37
- package/dist/modules/forms/submission.js.map +1 -1
- package/dist/modules/import-export/customFieldCsv.d.ts +9 -0
- package/dist/modules/import-export/customFieldCsv.d.ts.map +1 -0
- package/dist/modules/import-export/customFieldCsv.js +39 -0
- package/dist/modules/import-export/customFieldCsv.js.map +1 -0
- package/dist/modules/import-export/customFieldImport.d.ts +19 -0
- package/dist/modules/import-export/customFieldImport.d.ts.map +1 -0
- package/dist/modules/import-export/customFieldImport.js +76 -0
- package/dist/modules/import-export/customFieldImport.js.map +1 -0
- package/dist/modules/import-export/drafts.d.ts +57 -3
- package/dist/modules/import-export/drafts.d.ts.map +1 -1
- package/dist/modules/import-export/drafts.js +65 -3
- package/dist/modules/import-export/drafts.js.map +1 -1
- package/dist/modules/import-export/exportRows.d.ts +15 -13
- package/dist/modules/import-export/exportRows.d.ts.map +1 -1
- package/dist/modules/import-export/exportRows.js +99 -26
- package/dist/modules/import-export/exportRows.js.map +1 -1
- package/dist/modules/import-export/plan.d.ts +42 -11
- package/dist/modules/import-export/plan.d.ts.map +1 -1
- package/dist/modules/import-export/plan.js +198 -56
- package/dist/modules/import-export/plan.js.map +1 -1
- package/dist/modules/import-export/repository.d.ts +58 -2
- package/dist/modules/import-export/repository.d.ts.map +1 -1
- package/dist/modules/import-export/repository.js +201 -12
- package/dist/modules/import-export/repository.js.map +1 -1
- package/dist/modules/import-export/routes.d.ts.map +1 -1
- package/dist/modules/import-export/routes.js +5 -5
- package/dist/modules/import-export/routes.js.map +1 -1
- package/dist/modules/import-export/service.d.ts +8 -3
- package/dist/modules/import-export/service.d.ts.map +1 -1
- package/dist/modules/import-export/service.js +133 -40
- package/dist/modules/import-export/service.js.map +1 -1
- package/dist/modules/import-export/streams.d.ts +2 -2
- package/dist/modules/import-export/streams.d.ts.map +1 -1
- package/dist/modules/import-export/streams.js +50 -9
- package/dist/modules/import-export/streams.js.map +1 -1
- package/dist/modules/import-export/tools.d.ts.map +1 -1
- package/dist/modules/import-export/tools.js +13 -7
- package/dist/modules/import-export/tools.js.map +1 -1
- package/dist/modules/import-export/validation.d.ts +5 -1
- package/dist/modules/import-export/validation.d.ts.map +1 -1
- package/dist/modules/import-export/validation.js +89 -2
- package/dist/modules/import-export/validation.js.map +1 -1
- package/dist/modules/import-export/writes.d.ts +2 -1
- package/dist/modules/import-export/writes.d.ts.map +1 -1
- package/dist/modules/import-export/writes.js +358 -25
- package/dist/modules/import-export/writes.js.map +1 -1
- package/dist/modules/lists/repository.d.ts +0 -1
- package/dist/modules/lists/repository.d.ts.map +1 -1
- package/dist/modules/lists/repository.js +0 -1
- package/dist/modules/lists/repository.js.map +1 -1
- package/dist/modules/lists/routes.d.ts +0 -2
- package/dist/modules/lists/routes.d.ts.map +1 -1
- package/dist/modules/lists/routes.js +0 -8
- package/dist/modules/lists/routes.js.map +1 -1
- package/dist/modules/lists/schema.d.ts +0 -17
- package/dist/modules/lists/schema.d.ts.map +1 -1
- package/dist/modules/lists/schema.js +0 -10
- package/dist/modules/lists/schema.js.map +1 -1
- package/dist/modules/lists/service.d.ts +2 -14
- package/dist/modules/lists/service.d.ts.map +1 -1
- package/dist/modules/lists/service.js +7 -53
- package/dist/modules/lists/service.js.map +1 -1
- package/dist/modules/lists/starters.d.ts +12 -0
- package/dist/modules/lists/starters.d.ts.map +1 -0
- package/dist/modules/lists/starters.js +4 -0
- package/dist/modules/lists/starters.js.map +1 -0
- package/dist/modules/mcp/protocol.d.ts.map +1 -1
- package/dist/modules/mcp/protocol.js +13 -0
- package/dist/modules/mcp/protocol.js.map +1 -1
- package/dist/modules/notes/repository.d.ts +2 -0
- package/dist/modules/notes/repository.d.ts.map +1 -1
- package/dist/modules/notes/repository.js +9 -0
- package/dist/modules/notes/repository.js.map +1 -1
- package/dist/modules/opportunities/events.d.ts +9 -0
- package/dist/modules/opportunities/events.d.ts.map +1 -1
- package/dist/modules/opportunities/events.js +4 -0
- package/dist/modules/opportunities/events.js.map +1 -1
- package/dist/modules/opportunities/index.d.ts.map +1 -1
- package/dist/modules/opportunities/index.js +4 -2
- package/dist/modules/opportunities/index.js.map +1 -1
- package/dist/modules/opportunities/routes.d.ts +2 -0
- package/dist/modules/opportunities/routes.d.ts.map +1 -1
- package/dist/modules/opportunities/routes.js +8 -1
- package/dist/modules/opportunities/routes.js.map +1 -1
- package/dist/modules/opportunities/schema.d.ts +34 -0
- package/dist/modules/opportunities/schema.d.ts.map +1 -1
- package/dist/modules/opportunities/schema.js +7 -2
- package/dist/modules/opportunities/schema.js.map +1 -1
- package/dist/modules/opportunities/service.d.ts.map +1 -1
- package/dist/modules/opportunities/service.js +2 -0
- package/dist/modules/opportunities/service.js.map +1 -1
- package/dist/modules/opportunities/tools.d.ts +2 -1
- package/dist/modules/opportunities/tools.d.ts.map +1 -1
- package/dist/modules/opportunities/tools.js +3 -1
- package/dist/modules/opportunities/tools.js.map +1 -1
- package/dist/modules/partnerships/events.d.ts +9 -0
- package/dist/modules/partnerships/events.d.ts.map +1 -1
- package/dist/modules/partnerships/events.js +4 -0
- package/dist/modules/partnerships/events.js.map +1 -1
- package/dist/modules/partnerships/index.d.ts.map +1 -1
- package/dist/modules/partnerships/index.js +4 -2
- package/dist/modules/partnerships/index.js.map +1 -1
- package/dist/modules/partnerships/routes.d.ts +2 -0
- package/dist/modules/partnerships/routes.d.ts.map +1 -1
- package/dist/modules/partnerships/routes.js +8 -1
- package/dist/modules/partnerships/routes.js.map +1 -1
- package/dist/modules/partnerships/schema.d.ts +34 -0
- package/dist/modules/partnerships/schema.d.ts.map +1 -1
- package/dist/modules/partnerships/schema.js +7 -2
- package/dist/modules/partnerships/schema.js.map +1 -1
- package/dist/modules/partnerships/service.d.ts.map +1 -1
- package/dist/modules/partnerships/service.js +2 -0
- package/dist/modules/partnerships/service.js.map +1 -1
- package/dist/modules/partnerships/tools.d.ts +2 -1
- package/dist/modules/partnerships/tools.d.ts.map +1 -1
- package/dist/modules/partnerships/tools.js +3 -1
- package/dist/modules/partnerships/tools.js.map +1 -1
- package/dist/modules/people/personConsentWrites.d.ts +1 -1
- package/dist/modules/people/schema.js +1 -1
- package/dist/modules/people/schema.js.map +1 -1
- package/dist/modules/plans/repository.d.ts +2 -0
- package/dist/modules/plans/repository.d.ts.map +1 -1
- package/dist/modules/plans/repository.js +9 -0
- package/dist/modules/plans/repository.js.map +1 -1
- package/dist/modules/raises/events.d.ts +9 -0
- package/dist/modules/raises/events.d.ts.map +1 -1
- package/dist/modules/raises/events.js +4 -0
- package/dist/modules/raises/events.js.map +1 -1
- package/dist/modules/raises/index.d.ts.map +1 -1
- package/dist/modules/raises/index.js +4 -2
- package/dist/modules/raises/index.js.map +1 -1
- package/dist/modules/raises/routes.d.ts +2 -0
- package/dist/modules/raises/routes.d.ts.map +1 -1
- package/dist/modules/raises/routes.js +8 -1
- package/dist/modules/raises/routes.js.map +1 -1
- package/dist/modules/raises/schema.d.ts +34 -0
- package/dist/modules/raises/schema.d.ts.map +1 -1
- package/dist/modules/raises/schema.js +7 -2
- package/dist/modules/raises/schema.js.map +1 -1
- package/dist/modules/raises/service.d.ts.map +1 -1
- package/dist/modules/raises/service.js +2 -0
- package/dist/modules/raises/service.js.map +1 -1
- package/dist/modules/raises/tools.d.ts +2 -1
- package/dist/modules/raises/tools.d.ts.map +1 -1
- package/dist/modules/raises/tools.js +3 -1
- package/dist/modules/raises/tools.js.map +1 -1
- package/dist/modules/workspace/routes.d.ts +0 -2
- package/dist/modules/workspace/routes.d.ts.map +1 -1
- package/dist/modules/workspace/routes.js +0 -6
- package/dist/modules/workspace/routes.js.map +1 -1
- package/dist/modules/workspace/schema.d.ts +0 -34
- package/dist/modules/workspace/schema.d.ts.map +1 -1
- package/dist/modules/workspace/schema.js +0 -2
- package/dist/modules/workspace/schema.js.map +1 -1
- package/dist/modules/workspace/seedStarterForms.d.ts +17 -0
- package/dist/modules/workspace/seedStarterForms.d.ts.map +1 -0
- package/dist/modules/workspace/seedStarterForms.js +71 -0
- package/dist/modules/workspace/seedStarterForms.js.map +1 -0
- package/dist/modules/workspace/service.d.ts +0 -4
- package/dist/modules/workspace/service.d.ts.map +1 -1
- package/dist/modules/workspace/service.js +20 -10
- package/dist/modules/workspace/service.js.map +1 -1
- package/dist/modules/workspace/tools.js +1 -1
- package/dist/modules/workspace/tools.js.map +1 -1
- package/dist/runtime/module.d.ts +63 -1
- package/dist/runtime/module.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/registry.js +40 -2
- package/dist/runtime/registry.js.map +1 -1
- package/dist/testing/fixtures.d.ts.map +1 -1
- package/dist/testing/fixtures.js +1 -0
- package/dist/testing/fixtures.js.map +1 -1
- package/migrations/0037_drop_lists_consent_purpose_id.sql +2 -0
- package/migrations/0038_smart_white_queen.sql +2 -0
- package/migrations/0039_form_map_to_open.sql +1 -0
- package/migrations/0040_pipeline_converted_to.sql +20 -0
- package/migrations/0041_api_key_scopes.sql +1 -0
- package/migrations/0042_drop_workspace_identity.sql +2 -0
- package/migrations/0043_external_sign_in.sql +2 -0
- package/migrations/meta/0038_snapshot.json +6554 -0
- package/migrations/meta/_journal.json +49 -0
- package/package.json +2 -2
- package/src/app.ts +3 -0
- package/src/index.ts +13 -1
- package/src/lib/actor.ts +3 -1
- package/src/lib/apiKeyScopes.ts +213 -0
- package/src/lib/errors.ts +30 -0
- package/src/modules/activities/repository.ts +24 -0
- package/src/modules/agent-tasks/repository.ts +24 -0
- package/src/modules/api-keys/routes.ts +5 -0
- package/src/modules/api-keys/schema.ts +2 -0
- package/src/modules/api-keys/scopeMiddleware.ts +30 -0
- package/src/modules/api-keys/service.ts +36 -2
- package/src/modules/auth/credentials.ts +4 -0
- package/src/modules/auth/index.ts +25 -4
- package/src/modules/auth/repository.ts +2 -1
- package/src/modules/auth/routes.ts +2 -8
- package/src/modules/auth/schema.ts +12 -1
- package/src/modules/auth/service.ts +143 -6
- package/src/modules/auth/session.ts +14 -0
- package/src/modules/consent-purposes/index.ts +1 -1
- package/src/modules/consent-purposes/schema.ts +3 -3
- package/src/modules/consent-purposes/service.ts +7 -16
- package/src/modules/consent-purposes/starters.ts +1 -1
- package/src/modules/consent-purposes/tools.ts +1 -1
- package/src/modules/conversions/clearPointers.ts +81 -0
- package/src/modules/conversions/factory.ts +20 -0
- package/src/modules/conversions/fieldMap.ts +548 -0
- package/src/modules/conversions/index.ts +6 -0
- package/src/modules/conversions/mcp.ts +71 -0
- package/src/modules/conversions/repoint.ts +146 -0
- package/src/modules/conversions/routes.ts +68 -0
- package/src/modules/conversions/service.ts +310 -0
- package/src/modules/conversions/wire.ts +42 -0
- package/src/modules/deals/events.ts +9 -0
- package/src/modules/deals/index.ts +4 -2
- package/src/modules/deals/routes.ts +11 -1
- package/src/modules/deals/schema.ts +10 -2
- package/src/modules/deals/service.ts +3 -0
- package/src/modules/deals/tools.ts +9 -1
- package/src/modules/decisions/repository.ts +23 -1
- package/src/modules/enquiries/events.ts +8 -2
- package/src/modules/enquiries/index.ts +4 -2
- package/src/modules/enquiries/routes.ts +11 -13
- package/src/modules/enquiries/schema.ts +12 -3
- package/src/modules/enquiries/service.ts +3 -150
- package/src/modules/enquiries/tools.ts +9 -18
- package/src/modules/forms/applyMappedFields.ts +248 -0
- package/src/modules/forms/applySubmissionFields.ts +435 -0
- package/src/modules/forms/fields.ts +31 -7
- package/src/modules/forms/mapping.ts +20 -3
- package/src/modules/forms/repository.ts +0 -7
- package/src/modules/forms/routes.ts +1 -2
- package/src/modules/forms/schema.ts +0 -2
- package/src/modules/forms/service.ts +31 -4
- package/src/modules/forms/starters.ts +129 -0
- package/src/modules/forms/submission.ts +135 -69
- package/src/modules/import-export/customFieldCsv.ts +55 -0
- package/src/modules/import-export/customFieldImport.ts +111 -0
- package/src/modules/import-export/drafts.ts +151 -3
- package/src/modules/import-export/exportRows.ts +222 -68
- package/src/modules/import-export/plan.ts +321 -67
- package/src/modules/import-export/repository.ts +356 -14
- package/src/modules/import-export/routes.ts +8 -8
- package/src/modules/import-export/service.ts +208 -45
- package/src/modules/import-export/streams.ts +95 -13
- package/src/modules/import-export/tools.ts +14 -6
- package/src/modules/import-export/validation.ts +128 -0
- package/src/modules/import-export/writes.ts +559 -27
- package/src/modules/lists/repository.ts +0 -2
- package/src/modules/lists/routes.ts +0 -8
- package/src/modules/lists/schema.ts +0 -10
- package/src/modules/lists/service.ts +9 -107
- package/src/modules/lists/starters.ts +16 -0
- package/src/modules/mcp/protocol.ts +18 -0
- package/src/modules/notes/repository.ts +24 -0
- package/src/modules/opportunities/events.ts +9 -0
- package/src/modules/opportunities/index.ts +4 -2
- package/src/modules/opportunities/routes.ts +11 -1
- package/src/modules/opportunities/schema.ts +10 -2
- package/src/modules/opportunities/service.ts +3 -0
- package/src/modules/opportunities/tools.ts +9 -1
- package/src/modules/partnerships/events.ts +9 -0
- package/src/modules/partnerships/index.ts +4 -2
- package/src/modules/partnerships/routes.ts +11 -1
- package/src/modules/partnerships/schema.ts +10 -2
- package/src/modules/partnerships/service.ts +3 -0
- package/src/modules/partnerships/tools.ts +9 -1
- package/src/modules/people/personConsentWrites.ts +1 -1
- package/src/modules/people/schema.ts +1 -1
- package/src/modules/plans/repository.ts +24 -0
- package/src/modules/raises/events.ts +9 -0
- package/src/modules/raises/index.ts +4 -2
- package/src/modules/raises/routes.ts +11 -1
- package/src/modules/raises/schema.ts +10 -2
- package/src/modules/raises/service.ts +3 -0
- package/src/modules/raises/tools.ts +9 -1
- package/src/modules/workspace/routes.ts +0 -6
- package/src/modules/workspace/schema.ts +0 -2
- package/src/modules/workspace/seedStarterForms.ts +98 -0
- package/src/modules/workspace/service.ts +23 -15
- package/src/modules/workspace/tools.ts +1 -1
- package/src/runtime/module.ts +70 -1
- package/src/runtime/registry.ts +54 -0
- package/src/testing/fixtures.ts +1 -0
|
@@ -5,6 +5,7 @@ import type { KelpieModule } from '../../runtime/module.ts'
|
|
|
5
5
|
import { mountAuthRoutes } from './routes.ts'
|
|
6
6
|
import * as schema from './schema.ts'
|
|
7
7
|
import { createAuthService } from './service.ts'
|
|
8
|
+
import { describeClient, writeSessionCookie } from './session.ts'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Accounts, sessions, and password recovery.
|
|
@@ -38,6 +39,11 @@ export function createAuthModule(migrationsDirectory: string): KelpieModule {
|
|
|
38
39
|
appBaseUrl,
|
|
39
40
|
})
|
|
40
41
|
|
|
42
|
+
// `Secure` everywhere except development. A test host reaches the API
|
|
43
|
+
// over http through a test client, which stores the cookie regardless of
|
|
44
|
+
// the flag, so test is not excluded.
|
|
45
|
+
const cookie = { secure: NODE_ENV !== 'development' }
|
|
46
|
+
|
|
41
47
|
context.schema(schema, migrationsDirectory)
|
|
42
48
|
|
|
43
49
|
context.routes((router) => {
|
|
@@ -45,11 +51,26 @@ export function createAuthModule(migrationsDirectory: string): KelpieModule {
|
|
|
45
51
|
db: context.db,
|
|
46
52
|
now: context.now,
|
|
47
53
|
service,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
cookie,
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// How a module signs a browser in. It verifies an identity its own way
|
|
59
|
+
// and hands it here; everything from the cookie onwards is what a
|
|
60
|
+
// password sign-in does, through the same helper with the same flags.
|
|
61
|
+
context.provideExternalSignIn(async (honoContext, identity) => {
|
|
62
|
+
const issued = await service.completeExternalSignIn({
|
|
63
|
+
...identity,
|
|
64
|
+
...describeClient(honoContext),
|
|
52
65
|
})
|
|
66
|
+
|
|
67
|
+
writeSessionCookie(honoContext, issued.sessionToken, cookie)
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
account: issued.account,
|
|
71
|
+
created: issued.created,
|
|
72
|
+
activeWorkspaceId: issued.activeWorkspaceId,
|
|
73
|
+
}
|
|
53
74
|
})
|
|
54
75
|
|
|
55
76
|
return Promise.resolve()
|
|
@@ -48,10 +48,11 @@ export async function insertUser(db: Queryable, values: typeof users.$inferInser
|
|
|
48
48
|
return created
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/** `null` clears the password, leaving the account reachable only through a module or a reset. */
|
|
51
52
|
export async function updateUserPassword(
|
|
52
53
|
db: Queryable,
|
|
53
54
|
userId: string,
|
|
54
|
-
passwordHash: string,
|
|
55
|
+
passwordHash: string | null,
|
|
55
56
|
now: Date,
|
|
56
57
|
): Promise<void> {
|
|
57
58
|
await db
|
|
@@ -11,7 +11,7 @@ import { resolveActorFrom } from './credentials.ts'
|
|
|
11
11
|
import type { CredentialDependencies } from './credentials.ts'
|
|
12
12
|
import type { PreferenceValues } from './preferences.ts'
|
|
13
13
|
import type { AccountView, AuthService, IssuedSession, SessionView } from './service.ts'
|
|
14
|
-
import { clearSessionCookie, writeSessionCookie } from './session.ts'
|
|
14
|
+
import { clearSessionCookie, describeClient, writeSessionCookie } from './session.ts'
|
|
15
15
|
import type { SessionCookieOptions } from './session.ts'
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -108,13 +108,6 @@ async function readBody<T>(context: Context, schema: z.ZodType<T>): Promise<T> {
|
|
|
108
108
|
return parsed.data
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/** The device and location shown on the Security page come from the request itself. */
|
|
112
|
-
function describeClient(context: Context): { device?: string; location?: string } {
|
|
113
|
-
const userAgent = context.req.header('User-Agent')
|
|
114
|
-
|
|
115
|
-
return userAgent === undefined ? {} : { device: userAgent }
|
|
116
|
-
}
|
|
117
|
-
|
|
118
111
|
function accountResponse(issued: IssuedSession): Record<string, unknown> {
|
|
119
112
|
return {
|
|
120
113
|
account: accountBody(issued.account),
|
|
@@ -149,6 +142,7 @@ function sessionResponse(session: SessionView): Record<string, unknown> {
|
|
|
149
142
|
location: session.location,
|
|
150
143
|
last_active_at: session.lastActiveAt.toISOString(),
|
|
151
144
|
current: session.current,
|
|
145
|
+
signed_in_via: session.signedInVia,
|
|
152
146
|
}
|
|
153
147
|
}
|
|
154
148
|
|
|
@@ -18,7 +18,12 @@ export const users = pgTable('users', {
|
|
|
18
18
|
id: primaryId(),
|
|
19
19
|
email: citext('email').notNull().unique(),
|
|
20
20
|
name: text('name').notNull(),
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Null for an account that has only ever signed in through an identity
|
|
23
|
+
* module. Password sign-in is refused while it is null; a password is set
|
|
24
|
+
* through the reset flow, whose emailed link proves control of the address.
|
|
25
|
+
*/
|
|
26
|
+
passwordHash: text('password_hash'),
|
|
22
27
|
/** Null until the address is verified, either by confirming a token or by accepting a workspace invite. */
|
|
23
28
|
emailVerifiedAt: moment('email_verified_at'),
|
|
24
29
|
createdAt: createdAt(),
|
|
@@ -34,6 +39,12 @@ export const sessions = pgTable('sessions', {
|
|
|
34
39
|
tokenHash: text('token_hash').notNull().unique(),
|
|
35
40
|
device: text('device'),
|
|
36
41
|
location: text('location'),
|
|
42
|
+
/**
|
|
43
|
+
* Which module completed this sign-in (`sign-in:google`), null for a password
|
|
44
|
+
* one. The Security page lists sessions, and a person should be able to tell
|
|
45
|
+
* the two apart.
|
|
46
|
+
*/
|
|
47
|
+
signedInVia: text('signed_in_via'),
|
|
37
48
|
lastActiveAt: moment('last_active_at').notNull().defaultNow(),
|
|
38
49
|
expiresAt: moment('expires_at').notNull(),
|
|
39
50
|
createdAt: createdAt(),
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { APP_LINK_PATHS, buildAppLink } from '../../lib/appUrl.ts'
|
|
2
2
|
import type { EmailSender } from '../../lib/email.ts'
|
|
3
3
|
import { renderEmail } from '../../lib/emailContent.ts'
|
|
4
|
-
import { AppError } from '../../lib/errors.ts'
|
|
4
|
+
import { AppError, ExternalSignInError } from '../../lib/errors.ts'
|
|
5
5
|
import type { IdFactory } from '../../lib/ids.ts'
|
|
6
6
|
import { UNIQUE_VIOLATION, postgresErrorCode } from '../../lib/database.ts'
|
|
7
7
|
import type { Database } from '../../lib/database.ts'
|
|
8
8
|
import { MINIMUM_PASSWORD_LENGTH, hashPassword, verifyPassword } from '../../lib/passwords.ts'
|
|
9
9
|
import { generateToken, hashToken } from '../../lib/tokens.ts'
|
|
10
|
+
import type { VerifiedIdentity } from '../../runtime/module.ts'
|
|
10
11
|
import type { TransactionScope } from '../../runtime/transaction.ts'
|
|
11
12
|
import type { SessionActor } from './actor.ts'
|
|
12
13
|
import { DEFAULT_PREFERENCES, applyPreferenceChanges } from './preferences.ts'
|
|
@@ -54,12 +55,19 @@ export interface IssuedSession {
|
|
|
54
55
|
readonly activeWorkspaceId: string | null
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/** What `completeExternalSignIn` returns: an issued session plus whether it made the account. */
|
|
59
|
+
export interface ExternalIssuedSession extends IssuedSession {
|
|
60
|
+
readonly created: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
57
63
|
export interface SessionView {
|
|
58
64
|
readonly id: string
|
|
59
65
|
readonly device: string | null
|
|
60
66
|
readonly location: string | null
|
|
61
67
|
readonly lastActiveAt: Date
|
|
62
68
|
readonly current: boolean
|
|
69
|
+
/** The module that signed this session in, or null for a password sign-in. */
|
|
70
|
+
readonly signedInVia: string | null
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
export interface SignUpInput {
|
|
@@ -85,6 +93,12 @@ export interface LogInInput {
|
|
|
85
93
|
readonly location?: string
|
|
86
94
|
}
|
|
87
95
|
|
|
96
|
+
/** A `VerifiedIdentity` plus what the request itself says about the client. */
|
|
97
|
+
export interface ExternalSignInInput extends VerifiedIdentity {
|
|
98
|
+
readonly device?: string
|
|
99
|
+
readonly location?: string
|
|
100
|
+
}
|
|
101
|
+
|
|
88
102
|
function toAccountView(user: repository.UserRecord): AccountView {
|
|
89
103
|
return { id: user.id, email: user.email, name: user.name, emailVerified: user.emailVerifiedAt !== null }
|
|
90
104
|
}
|
|
@@ -105,6 +119,16 @@ function normaliseEmail(email: string): string {
|
|
|
105
119
|
return email.trim().toLowerCase()
|
|
106
120
|
}
|
|
107
121
|
|
|
122
|
+
/**
|
|
123
|
+
* The name to use when a provider sends none. `users.name` is not nullable and
|
|
124
|
+
* an address is the only other thing an identity is guaranteed to carry.
|
|
125
|
+
*/
|
|
126
|
+
function localPartOf(email: string): string {
|
|
127
|
+
const [local] = email.split('@')
|
|
128
|
+
|
|
129
|
+
return local === undefined || local.length === 0 ? email : local
|
|
130
|
+
}
|
|
131
|
+
|
|
108
132
|
/**
|
|
109
133
|
* Rejects a field that is only whitespace.
|
|
110
134
|
*
|
|
@@ -128,6 +152,12 @@ export interface AuthService {
|
|
|
128
152
|
/** Creates the account only. The first workspace comes from onboarding. */
|
|
129
153
|
signUp(input: SignUpInput): Promise<IssuedSession>
|
|
130
154
|
logIn(input: LogInInput): Promise<IssuedSession>
|
|
155
|
+
/**
|
|
156
|
+
* Signs in an identity a module verified elsewhere, provisioning the account
|
|
157
|
+
* when the module asked for it. Never sends a verification email: the
|
|
158
|
+
* provider already proved control of the address.
|
|
159
|
+
*/
|
|
160
|
+
completeExternalSignIn(input: ExternalSignInInput): Promise<ExternalIssuedSession>
|
|
131
161
|
logOut(actor: SessionActor): Promise<void>
|
|
132
162
|
getAccount(actor: SessionActor): Promise<AccountView>
|
|
133
163
|
updateAccount(actor: SessionActor, changes: UpdateAccountChanges): Promise<AccountView>
|
|
@@ -167,6 +197,8 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
167
197
|
user: repository.UserRecord,
|
|
168
198
|
device: string | undefined,
|
|
169
199
|
location: string | undefined,
|
|
200
|
+
/** The module that verified the identity, or null for a password sign-in. */
|
|
201
|
+
signedInVia: string | null = null,
|
|
170
202
|
): Promise<IssuedSession> {
|
|
171
203
|
const now = dependencies.now()
|
|
172
204
|
const token = newToken()
|
|
@@ -180,6 +212,7 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
180
212
|
tokenHash: hashToken(token),
|
|
181
213
|
device: device ?? null,
|
|
182
214
|
location: location ?? null,
|
|
215
|
+
signedInVia,
|
|
183
216
|
lastActiveAt: now,
|
|
184
217
|
expiresAt,
|
|
185
218
|
})
|
|
@@ -269,17 +302,113 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
269
302
|
async logIn(input: LogInInput): Promise<IssuedSession> {
|
|
270
303
|
const user = await repository.findUserByEmail(dependencies.db, normaliseEmail(input.email))
|
|
271
304
|
|
|
272
|
-
// Hash a throwaway password when the
|
|
273
|
-
//
|
|
305
|
+
// Hash a throwaway password when the account is unknown or has no
|
|
306
|
+
// password, so all three cases take the same time to answer.
|
|
274
307
|
const storedHash = user?.passwordHash ?? (await hashPassword('not-a-real-password-placeholder'))
|
|
275
|
-
|
|
276
|
-
|
|
308
|
+
// Always run the verify, even when the hash is the placeholder: skipping
|
|
309
|
+
// it would make a passwordless account answer faster than a wrong one.
|
|
310
|
+
const matches = await verifyPassword(storedHash, input.password)
|
|
311
|
+
|
|
312
|
+
// The null check is the guard, not the placeholder hash. Without it,
|
|
313
|
+
// whoever guessed the placeholder string would sign in to every account
|
|
314
|
+
// that has no password.
|
|
315
|
+
if (user === undefined || user.passwordHash === null || !matches) {
|
|
277
316
|
throw AppError.unauthorized('Email or password is incorrect')
|
|
278
317
|
}
|
|
279
318
|
|
|
280
319
|
return dependencies.transaction(({ tx }) => issueSession(tx, user, input.device, input.location))
|
|
281
320
|
},
|
|
282
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Signs in an identity a module already verified.
|
|
324
|
+
*
|
|
325
|
+
* Core does not redo the verification and never learns the protocol. What
|
|
326
|
+
* it owns is everything downstream: finding or provisioning the account,
|
|
327
|
+
* issuing the session, and recording which module vouched for it.
|
|
328
|
+
*/
|
|
329
|
+
async completeExternalSignIn(input: ExternalSignInInput): Promise<ExternalIssuedSession> {
|
|
330
|
+
// An unverified address is not an identity. A provider that does not say
|
|
331
|
+
// it checked is treated as not having checked.
|
|
332
|
+
if (!input.emailVerified) {
|
|
333
|
+
throw new ExternalSignInError('email_unverified')
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const email = requireText(normaliseEmail(input.email), 'email')
|
|
337
|
+
const name = requireText(input.name ?? localPartOf(email), 'name')
|
|
338
|
+
const now = dependencies.now()
|
|
339
|
+
|
|
340
|
+
const link = async (
|
|
341
|
+
tx: repository.Queryable,
|
|
342
|
+
user: repository.UserRecord,
|
|
343
|
+
): Promise<ExternalIssuedSession> => {
|
|
344
|
+
// Signing in through a provider on an account whose address was never
|
|
345
|
+
// confirmed is the pre-registration takeover case: somebody registered
|
|
346
|
+
// this address with a password and never proved they own it. The
|
|
347
|
+
// provider just proved the opposite. Drop that password and every
|
|
348
|
+
// session it opened; the real owner sets a new one through a reset.
|
|
349
|
+
if (user.emailVerifiedAt === null) {
|
|
350
|
+
await repository.updateUserPassword(tx, user.id, null, now)
|
|
351
|
+
await repository.deleteAllSessionsForUser(tx, user.id)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// The provider proved control of the address, which is the same thing
|
|
355
|
+
// accepting an invite proves.
|
|
356
|
+
await repository.markEmailVerified(tx, user.id, now)
|
|
357
|
+
|
|
358
|
+
const issued = await issueSession(
|
|
359
|
+
tx,
|
|
360
|
+
{ ...user, emailVerifiedAt: user.emailVerifiedAt ?? now },
|
|
361
|
+
input.device,
|
|
362
|
+
input.location,
|
|
363
|
+
input.verifiedBy,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
return { ...issued, created: false }
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const existing = await repository.findUserByEmail(dependencies.db, email)
|
|
370
|
+
|
|
371
|
+
if (existing !== undefined) {
|
|
372
|
+
return dependencies.transaction(({ tx }) => link(tx, existing))
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (input.provision === 'refuse') {
|
|
376
|
+
throw new ExternalSignInError('unknown_identity')
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return dependencies.transaction(async ({ tx }) => {
|
|
380
|
+
let user: repository.UserRecord
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
user = await repository.insertUser(tx, {
|
|
384
|
+
id: dependencies.createId('user'),
|
|
385
|
+
email,
|
|
386
|
+
name,
|
|
387
|
+
passwordHash: null,
|
|
388
|
+
emailVerifiedAt: now,
|
|
389
|
+
})
|
|
390
|
+
} catch (error: unknown) {
|
|
391
|
+
if (postgresErrorCode(error) !== UNIQUE_VIOLATION) {
|
|
392
|
+
throw error
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Two first sign-ins for the same address at once. The other one won;
|
|
396
|
+
// this is now an ordinary link.
|
|
397
|
+
const raced = await repository.findUserByEmail(tx, email)
|
|
398
|
+
|
|
399
|
+
if (raced === undefined) {
|
|
400
|
+
throw error
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return link(tx, raced)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const issued = await issueSession(tx, user, input.device, input.location, input.verifiedBy)
|
|
407
|
+
|
|
408
|
+
return { ...issued, created: true }
|
|
409
|
+
})
|
|
410
|
+
},
|
|
411
|
+
|
|
283
412
|
async logOut(actor: SessionActor): Promise<void> {
|
|
284
413
|
await repository.deleteSession(dependencies.db, actor.userId, actor.sessionId)
|
|
285
414
|
},
|
|
@@ -308,9 +437,12 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
308
437
|
: { email: requireText(normaliseEmail(changes.email), 'email') }),
|
|
309
438
|
}
|
|
310
439
|
|
|
440
|
+
// A passwordless account cannot prove itself this way. It sets a password
|
|
441
|
+
// through the reset flow first, which is the same bar this check is.
|
|
311
442
|
if (
|
|
312
443
|
changes.email !== undefined &&
|
|
313
444
|
(changes.currentPassword === undefined ||
|
|
445
|
+
user.passwordHash === null ||
|
|
314
446
|
!(await verifyPassword(user.passwordHash, changes.currentPassword)))
|
|
315
447
|
) {
|
|
316
448
|
throw AppError.unauthorized('Current password is incorrect')
|
|
@@ -405,6 +537,7 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
405
537
|
location: record.location,
|
|
406
538
|
lastActiveAt: record.lastActiveAt,
|
|
407
539
|
current: record.id === actor.sessionId,
|
|
540
|
+
signedInVia: record.signedInVia,
|
|
408
541
|
}))
|
|
409
542
|
},
|
|
410
543
|
|
|
@@ -494,7 +627,11 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
|
|
|
494
627
|
|
|
495
628
|
const user = await repository.findUserById(dependencies.db, actor.userId)
|
|
496
629
|
|
|
497
|
-
if (
|
|
630
|
+
if (
|
|
631
|
+
user === undefined ||
|
|
632
|
+
user.passwordHash === null ||
|
|
633
|
+
!(await verifyPassword(user.passwordHash, currentPassword))
|
|
634
|
+
) {
|
|
498
635
|
throw AppError.unauthorized('Current password is incorrect')
|
|
499
636
|
}
|
|
500
637
|
|
|
@@ -11,6 +11,20 @@ import type { Context } from 'hono'
|
|
|
11
11
|
|
|
12
12
|
export const SESSION_COOKIE = 'kelpie_session'
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The device and location shown on the Security page come from the request
|
|
16
|
+
* itself.
|
|
17
|
+
*
|
|
18
|
+
* Here rather than in `routes.ts` because the external sign-in handler needs
|
|
19
|
+
* the same description, and a session created by a module should look the same
|
|
20
|
+
* on that page as one created by the login form.
|
|
21
|
+
*/
|
|
22
|
+
export function describeClient(context: Context): { device?: string; location?: string } {
|
|
23
|
+
const userAgent = context.req.header('User-Agent')
|
|
24
|
+
|
|
25
|
+
return userAgent === undefined ? {} : { device: userAgent }
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
/** Thirty days, matching the session row's expiry. */
|
|
15
29
|
const COOKIE_MAX_AGE_SECONDS = 30 * 24 * 60 * 60
|
|
16
30
|
|
|
@@ -10,7 +10,7 @@ import { registerConsentPurposesTools } from './tools.ts'
|
|
|
10
10
|
* Consent purposes: the workspace catalog every capture site names.
|
|
11
11
|
*
|
|
12
12
|
* Structural because Person consent rows carry `purpose_id`, and forms /
|
|
13
|
-
*
|
|
13
|
+
* forms / imports each reference one; a disabled module would leave those
|
|
14
14
|
* references without a definition to resolve.
|
|
15
15
|
*
|
|
16
16
|
* Registers before people, forms, lists, and import-export so their `_fk`
|
|
@@ -7,9 +7,9 @@ import { workspaces } from '../workspace/schema.ts'
|
|
|
7
7
|
/**
|
|
8
8
|
* Workspace-defined consent purposes.
|
|
9
9
|
*
|
|
10
|
-
* Every capture site — a form's consent field,
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Every capture site — a form's consent field, an import job, the manual
|
|
11
|
+
* override on a Person — names one of these. `slug` is set at create and
|
|
12
|
+
* immutable after: a rename would strand every form/import that names it and
|
|
13
13
|
* names it and every `person_consents` row that carries it (identified by
|
|
14
14
|
* the purpose id). The strict PATCH body naturally refuses either as a `422`.
|
|
15
15
|
*
|
|
@@ -20,7 +20,6 @@ import type { Transaction, TransactionScope } from '../../runtime/transaction.ts
|
|
|
20
20
|
import type { Actor } from '../auth/actor.ts'
|
|
21
21
|
import { requireWorkspaceId } from '../auth/actor.ts'
|
|
22
22
|
import { formFields } from '../forms/schema.ts'
|
|
23
|
-
import { lists } from '../lists/schema.ts'
|
|
24
23
|
import { roleAllows } from '../workspace/roles.ts'
|
|
25
24
|
import { CONSENT_PURPOSES_LIMIT } from './capabilities.ts'
|
|
26
25
|
import './events.ts'
|
|
@@ -39,7 +38,7 @@ import type {
|
|
|
39
38
|
*
|
|
40
39
|
* Reads are open to any workspace member — every capture site needs to list
|
|
41
40
|
* them to render its purpose picker. Writes need the admin role: a purpose
|
|
42
|
-
* change touches every form
|
|
41
|
+
* change touches every form and import job that names it, and it is
|
|
43
42
|
* team-wide config rather than a per-user preference.
|
|
44
43
|
*/
|
|
45
44
|
|
|
@@ -143,17 +142,16 @@ function validateDefaultStatus(status: string): void {
|
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
/**
|
|
146
|
-
* A purpose still named by a form's consent field
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* name one.
|
|
145
|
+
* A purpose still named by a form's consent field cannot be deleted. Counted
|
|
146
|
+
* here so the 409 names the referencing type; the FK on either side would
|
|
147
|
+
* refuse the delete on its own but only name one.
|
|
150
148
|
*/
|
|
151
149
|
async function referencingTypes(
|
|
152
150
|
db: Database | Transaction,
|
|
153
151
|
workspaceId: string,
|
|
154
152
|
id: string,
|
|
155
153
|
): Promise<readonly string[]> {
|
|
156
|
-
const [fieldRow
|
|
154
|
+
const [fieldRow] = await Promise.all([
|
|
157
155
|
db
|
|
158
156
|
.select({ count: sql<number>`count(*)::int` })
|
|
159
157
|
.from(formFields)
|
|
@@ -165,19 +163,12 @@ async function referencingTypes(
|
|
|
165
163
|
sql`${id} = ANY(${formFields.consentPurposeIds})`,
|
|
166
164
|
),
|
|
167
165
|
),
|
|
168
|
-
db
|
|
169
|
-
.select({ count: sql<number>`count(*)::int` })
|
|
170
|
-
.from(lists)
|
|
171
|
-
.where(and(eq(lists.workspaceId, workspaceId), eq(lists.consentPurposeId, id))),
|
|
172
166
|
])
|
|
173
167
|
|
|
174
168
|
const referencing: string[] = []
|
|
175
169
|
if (Number(fieldRow[0]?.count ?? 0) > 0) {
|
|
176
170
|
referencing.push('form_field')
|
|
177
171
|
}
|
|
178
|
-
if (Number(listRow[0]?.count ?? 0) > 0) {
|
|
179
|
-
referencing.push('list')
|
|
180
|
-
}
|
|
181
172
|
return referencing
|
|
182
173
|
}
|
|
183
174
|
|
|
@@ -390,7 +381,7 @@ export function createConsentPurposesService(
|
|
|
390
381
|
const referencing = await referencingTypes(tx, workspaceId, id)
|
|
391
382
|
if (referencing.length > 0) {
|
|
392
383
|
throw AppError.conflict(
|
|
393
|
-
'Remove this purpose from every form
|
|
384
|
+
'Remove this purpose from every form field that names it before deleting it',
|
|
394
385
|
referencing.map((type) => ({ field: type, message: 'still references this purpose' })),
|
|
395
386
|
)
|
|
396
387
|
}
|
|
@@ -400,7 +391,7 @@ export function createConsentPurposesService(
|
|
|
400
391
|
} catch (error: unknown) {
|
|
401
392
|
if (postgresErrorCode(error) === FOREIGN_KEY_VIOLATION) {
|
|
402
393
|
throw AppError.conflict(
|
|
403
|
-
'Remove this purpose from every form
|
|
394
|
+
'Remove this purpose from every form field that names it before deleting it',
|
|
404
395
|
[{ field: 'id', message: 'still referenced' }],
|
|
405
396
|
)
|
|
406
397
|
}
|
|
@@ -5,7 +5,7 @@ import type { ConsentPurposeStatus } from '@kelpie/schemas'
|
|
|
5
5
|
* owner may rename, reorder, or delete them, and add any others they need.
|
|
6
6
|
*
|
|
7
7
|
* Both starters default to `unknown` — silence about a person's wishes is not
|
|
8
|
-
* a grant, and every capture site (forms,
|
|
8
|
+
* a grant, and every capture site (forms, imports, manual override)
|
|
9
9
|
* writes an explicit `person_consents` row when consent is actually given.
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -31,7 +31,7 @@ export function registerConsentPurposesTools(
|
|
|
31
31
|
subject: 'consent purpose',
|
|
32
32
|
about:
|
|
33
33
|
'A workspace-defined consent purpose. Read these before writing a person ' +
|
|
34
|
-
'consent or setting a form/
|
|
34
|
+
'consent or setting a form/import purpose_id.',
|
|
35
35
|
service,
|
|
36
36
|
render: consentPurposeResponse,
|
|
37
37
|
listArgs,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm'
|
|
2
|
+
import type { PipelineKind } from '@kelpie/schemas'
|
|
3
|
+
|
|
4
|
+
import type { Queryable } from '../../runtime/transaction.ts'
|
|
5
|
+
import { deals } from '../deals/schema.ts'
|
|
6
|
+
import { enquiries } from '../enquiries/schema.ts'
|
|
7
|
+
import { opportunities } from '../opportunities/schema.ts'
|
|
8
|
+
import { partnerships } from '../partnerships/schema.ts'
|
|
9
|
+
import { raises } from '../raises/schema.ts'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Clears `converted_target_*` on every pipeline record that pointed at a target
|
|
13
|
+
* being deleted. Enquiries also lose `converted_deal_id` when the target is a
|
|
14
|
+
* deal, matching the legacy FK `on delete set null` behaviour and allowing a
|
|
15
|
+
* fresh conversion once the created record is gone.
|
|
16
|
+
*/
|
|
17
|
+
export async function clearConversionPointersToTarget(
|
|
18
|
+
tx: Queryable,
|
|
19
|
+
workspaceId: string,
|
|
20
|
+
targetKind: PipelineKind,
|
|
21
|
+
targetId: string,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
const match = and(
|
|
24
|
+
eq(deals.workspaceId, workspaceId),
|
|
25
|
+
eq(deals.convertedTargetType, targetKind),
|
|
26
|
+
eq(deals.convertedTargetId, targetId),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
await tx
|
|
30
|
+
.update(enquiries)
|
|
31
|
+
.set({
|
|
32
|
+
convertedTargetType: null,
|
|
33
|
+
convertedTargetId: null,
|
|
34
|
+
...(targetKind === 'deal' ? { convertedDealId: null } : {}),
|
|
35
|
+
})
|
|
36
|
+
.where(
|
|
37
|
+
and(
|
|
38
|
+
eq(enquiries.workspaceId, workspaceId),
|
|
39
|
+
eq(enquiries.convertedTargetType, targetKind),
|
|
40
|
+
eq(enquiries.convertedTargetId, targetId),
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
await tx
|
|
45
|
+
.update(deals)
|
|
46
|
+
.set({ convertedTargetType: null, convertedTargetId: null })
|
|
47
|
+
.where(match)
|
|
48
|
+
|
|
49
|
+
await tx
|
|
50
|
+
.update(opportunities)
|
|
51
|
+
.set({ convertedTargetType: null, convertedTargetId: null })
|
|
52
|
+
.where(
|
|
53
|
+
and(
|
|
54
|
+
eq(opportunities.workspaceId, workspaceId),
|
|
55
|
+
eq(opportunities.convertedTargetType, targetKind),
|
|
56
|
+
eq(opportunities.convertedTargetId, targetId),
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
await tx
|
|
61
|
+
.update(raises)
|
|
62
|
+
.set({ convertedTargetType: null, convertedTargetId: null })
|
|
63
|
+
.where(
|
|
64
|
+
and(
|
|
65
|
+
eq(raises.workspaceId, workspaceId),
|
|
66
|
+
eq(raises.convertedTargetType, targetKind),
|
|
67
|
+
eq(raises.convertedTargetId, targetId),
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
await tx
|
|
72
|
+
.update(partnerships)
|
|
73
|
+
.set({ convertedTargetType: null, convertedTargetId: null })
|
|
74
|
+
.where(
|
|
75
|
+
and(
|
|
76
|
+
eq(partnerships.workspaceId, workspaceId),
|
|
77
|
+
eq(partnerships.convertedTargetType, targetKind),
|
|
78
|
+
eq(partnerships.convertedTargetId, targetId),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ModuleContext } from '../../runtime/module.ts'
|
|
2
|
+
import { createActivityRecorder } from '../activities/index.ts'
|
|
3
|
+
import { createCustomFieldValues } from '../custom-fields/index.ts'
|
|
4
|
+
import { createConversionsService } from './service.ts'
|
|
5
|
+
import type { ConversionsService } from './service.ts'
|
|
6
|
+
|
|
7
|
+
/** Build the shared conversion service from a module registration context. */
|
|
8
|
+
export function createConversionsFromContext(context: ModuleContext): ConversionsService {
|
|
9
|
+
return createConversionsService({
|
|
10
|
+
db: context.db,
|
|
11
|
+
transaction: context.transaction,
|
|
12
|
+
createId: context.createId,
|
|
13
|
+
now: context.now,
|
|
14
|
+
recordActivity: createActivityRecorder({
|
|
15
|
+
createId: context.createId,
|
|
16
|
+
now: context.now,
|
|
17
|
+
}),
|
|
18
|
+
customFields: createCustomFieldValues({ db: context.db }),
|
|
19
|
+
})
|
|
20
|
+
}
|