@futdevpro/nts-dynamo 1.15.13 → 1.15.15
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/.c8rc.json +26 -26
- package/.copilot/patterns.json +7 -7
- package/.cursor/rules/__assistant_guide.mdc +30 -30
- package/.cursor/rules/_ag_backend-structure.mdc +85 -85
- package/.cursor/rules/_ag_backend.mdc +16 -16
- package/.cursor/rules/_ag_frontend-structure.mdc +86 -86
- package/.cursor/rules/_ag_frontend.mdc +39 -39
- package/.cursor/rules/_ag_import-rules.mdc +44 -44
- package/.cursor/rules/_ag_naming.mdc +115 -115
- package/.cursor/rules/_ag_should-be.mdc +6 -6
- package/.cursor/rules/ai_development_guide.md +60 -60
- package/.cursor/rules/cursor-rules.md +160 -160
- package/.cursor/rules/default-command.mdc +464 -464
- package/.cursor/rules/error_code_pattern.md +39 -39
- package/.cursor/rules/saved rule mcp server use.md +15 -15
- package/.vscode/settings.json +10 -10
- package/HOWTO.md +15 -15
- package/LICENSE +21 -21
- package/__documentations/nts-integration-tests-2026-03-17.md +26 -26
- package/_specifications/BACKLOG.md +50 -22
- package/_specifications/TODO.md +15 -15
- package/_specifications/agent.md +138 -138
- package/build/_collections/global-settings.const.d.ts.map +1 -1
- package/build/_collections/global-settings.const.js +4 -0
- package/build/_collections/global-settings.const.js.map +1 -1
- package/build/_models/interfaces/global-log-settings.interface.d.ts +11 -0
- package/build/_models/interfaces/global-log-settings.interface.d.ts.map +1 -1
- package/build/_modules/logs/get-logs-routing-module.util.d.ts +19 -0
- package/build/_modules/logs/get-logs-routing-module.util.d.ts.map +1 -0
- package/build/_modules/logs/get-logs-routing-module.util.js +32 -0
- package/build/_modules/logs/get-logs-routing-module.util.js.map +1 -0
- package/build/_modules/logs/index.d.ts +4 -0
- package/build/_modules/logs/index.d.ts.map +1 -0
- package/build/_modules/logs/index.js +10 -0
- package/build/_modules/logs/index.js.map +1 -0
- package/build/_modules/logs/log-buffer.service.d.ts +38 -0
- package/build/_modules/logs/log-buffer.service.d.ts.map +1 -0
- package/build/_modules/logs/log-buffer.service.js +97 -0
- package/build/_modules/logs/log-buffer.service.js.map +1 -0
- package/build/_modules/logs/logs.controller.d.ts +27 -0
- package/build/_modules/logs/logs.controller.d.ts.map +1 -0
- package/build/_modules/logs/logs.controller.js +90 -0
- package/build/_modules/logs/logs.controller.js.map +1 -0
- package/build/_modules/logs/logs.service.d.ts +40 -0
- package/build/_modules/logs/logs.service.d.ts.map +1 -0
- package/build/_modules/logs/logs.service.js +97 -0
- package/build/_modules/logs/logs.service.js.map +1 -0
- package/build/_modules/server/errors/errors.data-service.d.ts.map +1 -1
- package/build/_modules/server/errors/errors.data-service.js +2 -1
- package/build/_modules/server/errors/errors.data-service.js.map +1 -1
- package/eslint.config.js +3 -3
- package/nodemon.json +24 -24
- package/package.json +362 -353
- package/scripts/run-coverage-tests.js +28 -28
- package/spec/support/helpers/spec-reporter-loader.js +359 -359
- package/spec/support/helpers/ts-node-helper.js +93 -93
- package/spec/support/jasmine.coverage.json +24 -24
- package/spec/support/jasmine.json +24 -24
- package/src/_collections/archive.util.spec.ts +57 -57
- package/src/_collections/archive.util.ts +18 -18
- package/src/_collections/atlas-default-db-options.const.ts +9 -9
- package/src/_collections/default-fallback-cache-max-age.const.spec.ts +11 -11
- package/src/_collections/default-fallback-cache-max-age.const.ts +2 -2
- package/src/_collections/default-not-found-page.const.spec.ts +19 -19
- package/src/_collections/default-not-found-page.const.ts +22 -22
- package/src/_collections/default-socket-path.const.spec.ts +12 -12
- package/src/_collections/default-socket-path.const.ts +2 -2
- package/src/_collections/get-environment-settings.util.spec.ts +210 -210
- package/src/_collections/get-environment-settings.util.ts +48 -48
- package/src/_collections/global-settings.const.ts +5 -0
- package/src/_collections/sample.env +21 -21
- package/src/_collections/star.controller.spec.ts +224 -224
- package/src/_collections/star.controller.ts +129 -129
- package/src/_enums/data-model-type.enum.ts +14 -14
- package/src/_enums/data-service-function.enum.ts +24 -24
- package/src/_enums/predefined-data-types.enum.ts +16 -16
- package/src/_enums/route-security.enum.ts +12 -12
- package/src/_models/control-models/api-call-params.control-model.spec.ts +152 -152
- package/src/_models/control-models/api-call-params.control-model.ts +142 -142
- package/src/_models/control-models/app-ext-system-controls.control-model.spec.ts +52 -52
- package/src/_models/control-models/app-ext-system-controls.control-model.ts +9 -9
- package/src/_models/control-models/app-params.control-model.spec.ts +225 -225
- package/src/_models/control-models/app-params.control-model.ts +136 -136
- package/src/_models/control-models/app-system-controls.control-model.spec.ts +31 -31
- package/src/_models/control-models/app-system-controls.control-model.ts +9 -9
- package/src/_models/control-models/endpoint-params.control-model.spec.ts +578 -578
- package/src/_models/control-models/endpoint-params.control-model.ts +526 -526
- package/src/_models/control-models/http-settings.control-model.spec.ts +77 -77
- package/src/_models/control-models/http-settings.control-model.ts +37 -37
- package/src/_models/control-models/system-control.control-model.spec.ts +27 -27
- package/src/_models/control-models/system-control.control-model.ts +12 -12
- package/src/_models/interfaces/certification-settings.interface.ts +7 -7
- package/src/_models/interfaces/environment-settings.interface.ts +59 -59
- package/src/_models/interfaces/global-log-settings.interface.ts +108 -96
- package/src/_models/interfaces/global-service-settings.interface.ts +47 -47
- package/src/_models/interfaces/routing-module-settings.interface.ts +21 -21
- package/src/_models/interfaces/static-client-settings.interface.spec.ts +29 -29
- package/src/_models/interfaces/static-client-settings.interface.ts +28 -28
- package/src/_models/types/db-update.type.ts +100 -100
- package/src/_modules/ai/_models/ai-input-interfaces.ts +117 -117
- package/src/_modules/ai/_models/ai-test-generation-result.interface.ts +16 -16
- package/src/_modules/ai/_modules/anthropic/_services/aai-user-key.control-service.ts +138 -138
- package/src/_modules/ai/_modules/anthropic/index.ts +5 -5
- package/src/_modules/ai/_modules/document-ai/_collections/dai-chunking.util.spec.ts +242 -242
- package/src/_modules/ai/_modules/document-ai/_collections/dai-chunking.util.ts +639 -639
- package/src/_modules/ai/_modules/document-ai/_collections/dai-document.util.spec.ts +209 -209
- package/src/_modules/ai/_modules/document-ai/_collections/dai-document.util.ts +85 -85
- package/src/_modules/ai/_modules/document-ai/_enums/dai-compare-result-type.enum.ts +7 -7
- package/src/_modules/ai/_modules/document-ai/_models/data-models/dai-doc-chunk.data-model.ts +146 -146
- package/src/_modules/ai/_modules/document-ai/_models/data-models/dai-doc-page.data-model.ts +162 -162
- package/src/_modules/ai/_modules/document-ai/_models/data-models/dai-document.data-model.ts +99 -99
- package/src/_modules/ai/_modules/document-ai/_models/interfaces/dai-doc-chunk-compare-result.interface.ts +18 -18
- package/src/_modules/ai/_modules/document-ai/_models/interfaces/dai-doc-page-compare-result.interface.ts +19 -19
- package/src/_modules/ai/_modules/document-ai/_models/interfaces/dai-document-compare-result.interface.ts +25 -25
- package/src/_modules/ai/_modules/document-ai/index.ts +28 -28
- package/src/_modules/ai/_modules/fdp-ai/_services/fdpai-user-key.control-service.ts +189 -189
- package/src/_modules/ai/_modules/fdp-ai/index.ts +5 -5
- package/src/_modules/ai/_modules/open-ai/_collections/oai-global-settings.const.ts +9 -9
- package/src/_modules/ai/_modules/open-ai/_collections/oai-llm-predefined-requests-hu.conts.ts +82 -82
- package/src/_modules/ai/_modules/open-ai/_collections/oai-llm-predefined-requests.conts.ts +75 -75
- package/src/_modules/ai/_modules/open-ai/_enums/oai-gpt-message-role.enum.ts +45 -45
- package/src/_modules/ai/_modules/open-ai/_models/interfaces/oai-global-settings.interface.ts +7 -7
- package/src/_modules/ai/_modules/open-ai/_models/interfaces/oai-gpt-message.interface.ts +7 -7
- package/src/_modules/ai/_modules/open-ai/_models/interfaces/oai-llm-predefined-requests.interface.ts +57 -57
- package/src/_modules/ai/_modules/open-ai/_services/data-services/oai-doc-chunk-data.service.ts +292 -292
- package/src/_modules/ai/_modules/open-ai/_services/data-services/oai-document.data-service.spec.ts +342 -342
- package/src/_modules/ai/_modules/open-ai/_services/data-services/oai-vector-data.service.spec.ts +550 -550
- package/src/_modules/ai/_modules/open-ai/_services/data-services/oai-vector-data.service.ts +630 -630
- package/src/_modules/ai/_modules/open-ai/_services/oai-embedding.control-service.spec.ts +240 -240
- package/src/_modules/ai/_modules/open-ai/_services/oai-embedding.control-service.ts +98 -98
- package/src/_modules/ai/_modules/open-ai/_services/oai-llm-chat.service-base.spec.ts +462 -462
- package/src/_modules/ai/_modules/open-ai/_services/oai-llm-chat.service-base.ts +615 -615
- package/src/_modules/ai/_modules/open-ai/_services/oai-llm.service-base.spec.ts +437 -437
- package/src/_modules/ai/_modules/open-ai/_services/oai-llm.service-base.ts +833 -833
- package/src/_modules/ai/_modules/open-ai/_services/oai-user-key.control-service.ts +157 -157
- package/src/_modules/ai/_services/ai-embedding.service-base.spec.ts +98 -98
- package/src/_modules/ai/_services/ai-embedding.service-base.ts +48 -48
- package/src/_modules/ai/_services/ai-llm-chat.service-base.spec.ts +229 -229
- package/src/_modules/ai/_services/ai-llm-chat.service-base.ts +68 -68
- package/src/_modules/ai/_services/ai-llm.service-base.spec.ts +250 -250
- package/src/_modules/ai/_services/ai-llm.service-base.ts +332 -332
- package/src/_modules/ai/_services/ai-provider.service-base.spec.ts +79 -79
- package/src/_modules/ai/_services/ai-provider.service-base.ts +29 -29
- package/src/_modules/ai/_services/ai-user-key.service-base.ts +59 -59
- package/src/_modules/ai/index.ts +13 -13
- package/src/_modules/assistant/_collections/ass-global-settings.const.ts +13 -13
- package/src/_modules/assistant/_collections/ass.util.spec.ts +176 -176
- package/src/_modules/assistant/_collections/ass.util.ts +50 -50
- package/src/_modules/assistant/_models/ass-global-settings.interface.ts +15 -15
- package/src/_modules/assistant/_services/ass-io.control-service.spec.ts +140 -140
- package/src/_modules/assistant/_services/ass-main.control-service.spec.ts +192 -192
- package/src/_modules/assistant/_services/ass-main.control-service.ts +107 -107
- package/src/_modules/bot/_collections/bot-default-commands.const.ts +12 -12
- package/src/_modules/bot/_collections/bot-global-settings.const.ts +39 -39
- package/src/_modules/bot/_models/bot-channel-wrapper.interface.ts +62 -62
- package/src/_modules/bot/_models/bot-command.interface.ts +8 -8
- package/src/_modules/bot/_models/bot-global-settings.interface.ts +96 -96
- package/src/_modules/bot/_models/bot-last-mention-date.interface.ts +6 -6
- package/src/_modules/bot/_models/bot-last-message-date.interface.ts +5 -5
- package/src/_modules/bot/_models/bot-user-wrapper.interface.ts +41 -41
- package/src/_modules/bot/_modules/discord-bot/_models/dib-platform.types.ts +9 -9
- package/src/_modules/bot/_modules/discord-bot/_services/dib-messaging-provider.control-service.spec.ts +431 -431
- package/src/_modules/bot/_modules/dynamo-bot/_collections/dyb-operations.util.spec.ts +160 -160
- package/src/_modules/bot/_modules/dynamo-bot/_collections/dyb-operations.util.ts +55 -55
- package/src/_modules/bot/_modules/dynamo-bot/_models/dyb-platform.types.ts +15 -15
- package/src/_modules/bot/_modules/dynamo-bot/_services/dyb-messaging-provider.control-service.spec.ts +374 -374
- package/src/_modules/bot/_modules/dynamo-bot/_services/dyb-messaging-provider.control-service.ts +447 -447
- package/src/_modules/bot/_modules/dynamo-bot/index.ts +15 -15
- package/src/_modules/bot/_modules/slack-bot/_models/slb-platform.types.ts +9 -9
- package/src/_modules/bot/_modules/slack-bot/_services/slb-messaging-provider.control-service.spec.ts +344 -344
- package/src/_modules/bot/_modules/slack-bot/_services/slb-messaging-provider.control-service.ts +197 -197
- package/src/_modules/bot/_modules/teams-bot/_models/teb-platform.types.ts +9 -9
- package/src/_modules/bot/_modules/teams-bot/_services/teb-messaging-provider.control-service.spec.ts +345 -345
- package/src/_modules/bot/_modules/teams-bot/_services/teb-messaging-provider.control-service.ts +197 -197
- package/src/_modules/bot/_services/bot-commands.control-service.spec.ts +116 -116
- package/src/_modules/bot/_services/bot-io.control-service.spec.ts +285 -285
- package/src/_modules/bot/_services/bot-main.control-service.spec.ts +208 -208
- package/src/_modules/bot/_services/bot-messaging-provider.service-base.spec.ts +349 -349
- package/src/_modules/bot/_services/bot-routines.control-service.spec.ts +111 -111
- package/src/_modules/custom-data/custom-data.controller.spec.ts +49 -49
- package/src/_modules/custom-data/custom-data.controller.ts +67 -67
- package/src/_modules/custom-data/custom-data.data-service.spec.ts +54 -54
- package/src/_modules/custom-data/custom-data.data-service.ts +21 -21
- package/src/_modules/custom-data/get-custom-data-routing-module.util.spec.ts +28 -28
- package/src/_modules/custom-data/get-custom-data-routing-module.util.ts +24 -24
- package/src/_modules/custom-data/index.ts +9 -9
- package/src/_modules/defaults/_collections/default-endpoints.util.ts +487 -487
- package/src/_modules/defaults/_models/default-user.data-model.ts +72 -72
- package/src/_modules/defaults/_services/default-auth.service.spec.ts +269 -269
- package/src/_modules/defaults/_services/default-auth.service.ts +177 -177
- package/src/_modules/defaults/_services/default-socket-events.service.spec.ts +42 -42
- package/src/_modules/defaults/_services/default-socket-events.service.ts +61 -61
- package/src/_modules/defaults/_services/default-user.data-service.spec.ts +187 -187
- package/src/_modules/defaults/_services/default-user.data-service.ts +98 -98
- package/src/_modules/defaults/index.ts +17 -17
- package/src/_modules/discord-assistant/_collections/dias-global-settings.const.ts +19 -19
- package/src/_modules/discord-assistant/_collections/dias.util.spec.ts +366 -366
- package/src/_modules/discord-assistant/_collections/dias.util.ts +132 -132
- package/src/_modules/discord-assistant/_models/dias-global-settings.interface.ts +19 -19
- package/src/_modules/discord-assistant/_models/dias-knowledge.data-model.ts +52 -52
- package/src/_modules/discord-assistant/_services/dias-chunk.data-service.ts +177 -177
- package/src/_modules/discord-assistant/_services/dias-io.control-service.spec.ts +108 -108
- package/src/_modules/discord-assistant/_services/dias-io.control-service.ts +69 -69
- package/src/_modules/discord-assistant/_services/dias-main.control-service.spec.ts +22 -22
- package/src/_modules/discord-assistant/_services/dias-main.control-service.ts +27 -27
- package/src/_modules/discord-assistant/_services/dias.service-base.spec.ts +195 -195
- package/src/_modules/discord-assistant/_services/dias.service-base.ts +76 -76
- package/src/_modules/discord-assistant/index.ts +38 -38
- package/src/_modules/discord-assistant-voiced/_services/dias-discord-bot.control-service.spec.ts +34 -34
- package/src/_modules/discord-assistant-voiced/_services/dias-discord-bot.control-service.ts +11 -11
- package/src/_modules/discord-assistant-voiced/index.ts +36 -36
- package/src/_modules/discord-bot/_collections/dibo-default-commands.const.ts +16 -16
- package/src/_modules/discord-bot/_collections/dibo-global-settings.conts.ts +55 -55
- package/src/_modules/discord-bot/_collections/dibo-operations.util.spec.ts +214 -214
- package/src/_modules/discord-bot/_collections/dibo-operations.util.ts +387 -387
- package/src/_modules/discord-bot/_models/dibo-command.interface.ts +12 -12
- package/src/_modules/discord-bot/_models/dibo-global-settings.interface.ts +98 -98
- package/src/_modules/discord-bot/_models/dibo-last-mention-date.inteface.ts +7 -7
- package/src/_modules/discord-bot/_models/dibo-last-message-date.interface.ts +6 -6
- package/src/_modules/discord-bot/_services/dibo-commands.control-service.spec.ts +154 -154
- package/src/_modules/discord-bot/_services/dibo-commands.control-service.ts +153 -153
- package/src/_modules/discord-bot/_services/dibo-io.control-service.spec.ts +264 -264
- package/src/_modules/discord-bot/_services/dibo-io.control-service.ts +306 -306
- package/src/_modules/discord-bot/_services/dibo-main.control-service.spec.ts +408 -408
- package/src/_modules/discord-bot/_services/dibo-main.control-service.ts +487 -487
- package/src/_modules/discord-bot/_services/dibo-routines.control-service.spec.ts +105 -105
- package/src/_modules/discord-bot/index.ts +36 -36
- package/src/_modules/local-vector-search/_enums/lvs-search-mode.enum.ts +19 -19
- package/src/_modules/local-vector-search/_models/lvs-search-result.interface.ts +17 -17
- package/src/_modules/local-vector-search/_services/lvs-doc-chunk-data.service.spec.ts +418 -418
- package/src/_modules/local-vector-search/_services/lvs-doc-chunk-data.service.ts +276 -276
- package/src/_modules/local-vector-search/_services/lvs-local-vector-search.data-service.spec.ts +345 -345
- package/src/_modules/local-vector-search/_services/lvs-local-vector-search.data-service.ts +330 -330
- package/src/_modules/local-vector-search/_services/lvs-vector-pool.control-service.spec.ts +393 -393
- package/src/_modules/local-vector-search/_services/lvs-vector-pool.control-service.ts +220 -220
- package/src/_modules/local-vector-search/index.ts +11 -11
- package/src/_modules/logs/get-logs-routing-module.util.ts +36 -0
- package/src/_modules/logs/index.ts +3 -0
- package/src/_modules/logs/log-buffer.service.ts +101 -0
- package/src/_modules/logs/logs.controller.ts +109 -0
- package/src/_modules/logs/logs.service.ts +100 -0
- package/src/_modules/messaging/README.md +354 -354
- package/src/_modules/messaging/_collections/get-messaging-routing-module.util.ts +26 -26
- package/src/_modules/messaging/_collections/msg-global-settings.const.ts +22 -22
- package/src/_modules/messaging/_collections/msg.util.spec.ts +226 -226
- package/src/_modules/messaging/_models/msg-global-settings.interface.ts +37 -37
- package/src/_modules/messaging/_services/msg-conversation.data-service.ts +146 -146
- package/src/_modules/messaging/_services/msg-events.service.spec.ts +219 -219
- package/src/_modules/messaging/_services/msg-events.service.ts +267 -267
- package/src/_modules/messaging/_services/msg-integration.control-service.ts +179 -179
- package/src/_modules/messaging/_services/msg-main.control-service.spec.ts +147 -147
- package/src/_modules/messaging/_services/msg-main.control-service.ts +571 -571
- package/src/_modules/messaging/_services/msg-message.data-service.ts +129 -129
- package/src/_modules/messaging/_services/msg.controller.spec.ts +201 -201
- package/src/_modules/messaging/index.ts +30 -30
- package/src/_modules/mock/app-extended-server.mock.ts +201 -201
- package/src/_modules/mock/app-integration-test.mock.ts +51 -51
- package/src/_modules/mock/app-params.mock.spec.ts +21 -21
- package/src/_modules/mock/app-params.mock.ts +9 -9
- package/src/_modules/mock/app-server.mock.ts +188 -188
- package/src/_modules/mock/auth-service.mock.spec.ts +47 -47
- package/src/_modules/mock/auth-service.mock.ts +28 -28
- package/src/_modules/mock/controller.mock.spec.ts +26 -26
- package/src/_modules/mock/controller.mock.ts +16 -16
- package/src/_modules/mock/data-model.mock.spec.ts +111 -111
- package/src/_modules/mock/data-model.mock.ts +82 -82
- package/src/_modules/mock/email-service-collection.mock.spec.ts +24 -24
- package/src/_modules/mock/email-service-collection.mock.ts +15 -15
- package/src/_modules/mock/email-service.mock.spec.ts +17 -17
- package/src/_modules/mock/email-service.mock.ts +20 -20
- package/src/_modules/mock/email-template.mock.html +14 -14
- package/src/_modules/mock/endpoint.mock.ts +91 -91
- package/src/_modules/mock/socket-client.mock.spec.ts +40 -40
- package/src/_modules/mock/socket-client.mock.ts +45 -45
- package/src/_modules/mock/socket-server.mock.spec.ts +44 -44
- package/src/_modules/mock/socket-server.mock.ts +46 -46
- package/src/_modules/oauth2/_routes/oauth2.controller.spec.ts +107 -107
- package/src/_modules/oauth2/_routes/oauth2.controller.ts +98 -98
- package/src/_modules/oauth2/_services/oauth2.auth-service.spec.ts +254 -254
- package/src/_modules/oauth2/_services/oauth2.auth-service.ts +232 -232
- package/src/_modules/oauth2/_services/oauth2.control-service.spec.ts +585 -585
- package/src/_modules/oauth2/_services/oauth2.control-service.ts +653 -653
- package/src/_modules/oauth2/index.ts +17 -17
- package/src/_modules/server/errors/errors.control-service.spec.ts +230 -230
- package/src/_modules/server/errors/errors.control-service.ts +69 -69
- package/src/_modules/server/errors/errors.controller.spec.ts +165 -165
- package/src/_modules/server/errors/errors.controller.ts +270 -270
- package/src/_modules/server/errors/errors.data-service.spec.ts +355 -355
- package/src/_modules/server/errors/errors.data-service.ts +2 -1
- package/src/_modules/server/index.ts +30 -30
- package/src/_modules/server/server-status/server-status-snapshot.control-service.spec.ts +70 -70
- package/src/_modules/server/server-status/server-status-snapshot.control-service.ts +17 -17
- package/src/_modules/server/server-status/server-status-snapshot.data-service.spec.ts +77 -77
- package/src/_modules/server/server-status/server-status-snapshot.data-service.ts +37 -37
- package/src/_modules/server/server-status/server-status.control-service.spec.ts +516 -516
- package/src/_modules/server/server-status/server-status.control-service.ts +336 -336
- package/src/_modules/server/server-status/server-status.controller.spec.ts +156 -156
- package/src/_modules/server/server-status/server-status.controller.ts +131 -131
- package/src/_modules/socket/_enums/socket-security.enum.ts +11 -11
- package/src/_modules/socket/_models/socket-client-service-params.control-model.spec.ts +32 -32
- package/src/_modules/socket/_models/socket-client-service-params.control-model.ts +22 -22
- package/src/_modules/socket/_models/socket-presence.control-model.spec.ts +164 -164
- package/src/_modules/socket/_models/socket-presence.control-model.ts +210 -210
- package/src/_modules/socket/_models/socket-server-service-params.control-model.spec.ts +46 -46
- package/src/_modules/socket/_models/socket-server-service-params.control-model.ts +22 -22
- package/src/_modules/socket/_services/socket-client.service.spec.ts +15 -15
- package/src/_modules/socket/_services/socket-client.service.ts +260 -260
- package/src/_modules/socket/_services/socket-server.service.spec.ts +11 -11
- package/src/_modules/socket/app-extended.integration.spec.ts +85 -85
- package/src/_modules/socket/app-extended.server.ts +630 -630
- package/src/_modules/socket/index.ts +42 -42
- package/src/_modules/test/get-test-routing-module.util.spec.ts +28 -28
- package/src/_modules/test/get-test-routing-module.util.ts +23 -23
- package/src/_modules/test/index.ts +11 -11
- package/src/_modules/test/test.controller.spec.ts +72 -72
- package/src/_modules/test/test.controller.ts +115 -115
- package/src/_modules/usage/get-usage-routing-module.util.ts +22 -22
- package/src/_modules/usage/index.ts +15 -15
- package/src/_modules/usage/usage.controller.spec.ts +81 -81
- package/src/_modules/usage/usage.controller.ts +126 -126
- package/src/_modules/usage/usage.data-service.spec.ts +332 -332
- package/src/_modules/usage/usage.data-service.ts +185 -185
- package/src/_services/base/api.service-base.spec.ts +125 -125
- package/src/_services/base/api.service-base.ts +74 -74
- package/src/_services/base/archive-data.service.spec.ts +196 -196
- package/src/_services/base/archive-data.service.ts +216 -216
- package/src/_services/base/data.service.spec.ts +493 -493
- package/src/_services/base/data.service.ts +2525 -2525
- package/src/_services/base/db.service.spec.ts +73 -73
- package/src/_services/base/db.service.ts +1575 -1575
- package/src/_services/base/singleton.service-base.spec.ts +28 -28
- package/src/_services/base/singleton.service-base.ts +24 -24
- package/src/_services/base/singleton.service.spec.ts +114 -114
- package/src/_services/base/singleton.service.ts +38 -38
- package/src/_services/core/api.service.spec.ts +140 -140
- package/src/_services/core/auth.service.spec.ts +159 -159
- package/src/_services/core/auth.service.ts +174 -174
- package/src/_services/core/email.service.spec.ts +85 -85
- package/src/_services/core/email.service.ts +742 -742
- package/src/_services/core/global.service.spec.ts +275 -275
- package/src/_services/core/global.service.ts +461 -461
- package/src/_services/core/service-collection.service.spec.ts +46 -46
- package/src/_services/core/service-collection.service.ts +6 -6
- package/src/_services/route/controller.service.spec.ts +53 -53
- package/src/_services/route/controller.service.ts +148 -148
- package/src/_services/route/routing-module.service.spec.ts +98 -98
- package/src/_services/route/routing-module.service.ts +330 -330
- package/src/_services/shared.static-service.spec.ts +99 -99
- package/src/_services/shared.static-service.ts +78 -78
- package/src/index.ts +94 -94
- package/tsconfig.app.json +12 -12
- package/tsconfig.json +42 -42
|
@@ -1,654 +1,654 @@
|
|
|
1
|
-
import { Request, Response } from 'express';
|
|
2
|
-
import { cryptoJs } from 'crypto-js';
|
|
3
|
-
import { DyFM_Error, DyFM_Log } from '@futdevpro/fsm-dynamo';
|
|
4
|
-
import { DyNTS_SingletonService } from '../../../_services/base/singleton.service';
|
|
5
|
-
import { DyNTS_global_settings } from '../../../_collections/global-settings.const';
|
|
6
|
-
import { DyNTS_OAuth2_AuthService } from './oauth2.auth-service';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* OAuth2 Control Service implementation
|
|
10
|
-
*
|
|
11
|
-
* This service handles OAuth2 specific business logic and token management
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* const oauth2Service = DyNTS_OAuth2_ControlService.getInstance();
|
|
15
|
-
* await oauth2Service.handleAuthorizationRequest(req, res);
|
|
16
|
-
*/
|
|
17
|
-
export class DyNTS_OAuth2_ControlService extends DyNTS_SingletonService {
|
|
18
|
-
static getInstance(): DyNTS_OAuth2_ControlService {
|
|
19
|
-
return DyNTS_OAuth2_ControlService.getSingletonInstance();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
readonly serviceName: string = 'OAuth2ControlService';
|
|
23
|
-
|
|
24
|
-
private readonly authService: DyNTS_OAuth2_AuthService = DyNTS_OAuth2_AuthService.getInstance();
|
|
25
|
-
private readonly authorizationCodes: Map<string, { clientId: string; scope: string; expiresAt: number }> = new Map();
|
|
26
|
-
private readonly accessTokens: Map<string, { clientId: string; scope: string; expiresAt: number }> = new Map();
|
|
27
|
-
private readonly refreshTokens: Map<string, { clientId: string; scope: string; accessToken: string }> = new Map();
|
|
28
|
-
private readonly clients: Map<string, {
|
|
29
|
-
clientId: string;
|
|
30
|
-
clientSecret: string;
|
|
31
|
-
redirectUris: string[];
|
|
32
|
-
allowedScopes: string[];
|
|
33
|
-
isActive: boolean;
|
|
34
|
-
}> = new Map();
|
|
35
|
-
private readonly users: Map<string, {
|
|
36
|
-
username: string;
|
|
37
|
-
password: string; // In a real implementation, this would be a hashed password
|
|
38
|
-
scopes: string[];
|
|
39
|
-
}> = new Map();
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Handles the OAuth2 authorization request
|
|
43
|
-
* @param req Express Request object
|
|
44
|
-
* @param res Express Response object
|
|
45
|
-
*/
|
|
46
|
-
async handleAuthorizationRequest(req: Request, res: Response): Promise<void> {
|
|
47
|
-
try {
|
|
48
|
-
const { response_type, client_id, redirect_uri, scope, state } = req.query;
|
|
49
|
-
|
|
50
|
-
// Validate required parameters
|
|
51
|
-
if (!response_type || !client_id || !redirect_uri) {
|
|
52
|
-
throw new DyFM_Error({
|
|
53
|
-
status: 400,
|
|
54
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HA0`,
|
|
55
|
-
addECToUserMsg: true,
|
|
56
|
-
message: 'Missing required OAuth2 parameters',
|
|
57
|
-
userMessage: 'Invalid authorization request',
|
|
58
|
-
issuerService: this.serviceName,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Validate client_id against registered clients
|
|
63
|
-
if (!this.isValidClient(client_id as string)) {
|
|
64
|
-
throw new DyFM_Error({
|
|
65
|
-
status: 400,
|
|
66
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HA2`,
|
|
67
|
-
addECToUserMsg: true,
|
|
68
|
-
message: 'Invalid client_id',
|
|
69
|
-
userMessage: 'Invalid authorization request',
|
|
70
|
-
issuerService: this.serviceName,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Validate redirect_uri against registered redirect URIs
|
|
75
|
-
if (!this.isValidRedirectUri(client_id as string, redirect_uri as string)) {
|
|
76
|
-
throw new DyFM_Error({
|
|
77
|
-
status: 400,
|
|
78
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HA3`,
|
|
79
|
-
addECToUserMsg: true,
|
|
80
|
-
message: 'Invalid redirect_uri',
|
|
81
|
-
userMessage: 'Invalid authorization request',
|
|
82
|
-
issuerService: this.serviceName,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Validate scope against allowed scopes
|
|
87
|
-
if (!this.isValidScope(client_id as string, scope as string)) {
|
|
88
|
-
throw new DyFM_Error({
|
|
89
|
-
status: 400,
|
|
90
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HA4`,
|
|
91
|
-
addECToUserMsg: true,
|
|
92
|
-
message: 'Invalid scope',
|
|
93
|
-
userMessage: 'Invalid authorization request',
|
|
94
|
-
issuerService: this.serviceName,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// For authorization code flow
|
|
99
|
-
if (response_type === 'code') {
|
|
100
|
-
const authorizationCode = await this.generateAuthorizationCode(client_id as string, scope as string);
|
|
101
|
-
|
|
102
|
-
// Redirect with authorization code
|
|
103
|
-
const redirectUrl = new URL(redirect_uri as string);
|
|
104
|
-
redirectUrl.searchParams.append('code', authorizationCode);
|
|
105
|
-
if (state) redirectUrl.searchParams.append('state', state as string);
|
|
106
|
-
|
|
107
|
-
res.redirect(redirectUrl.toString());
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// For implicit flow
|
|
112
|
-
if (response_type === 'token') {
|
|
113
|
-
const accessToken = await this.generateAccessToken(client_id as string, scope as string);
|
|
114
|
-
|
|
115
|
-
// Redirect with access token
|
|
116
|
-
const redirectUrl = new URL(redirect_uri as string);
|
|
117
|
-
redirectUrl.hash = `access_token=${accessToken}`;
|
|
118
|
-
if (state) redirectUrl.hash += `&state=${state}`;
|
|
119
|
-
|
|
120
|
-
res.redirect(redirectUrl.toString());
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
throw new DyFM_Error({
|
|
125
|
-
status: 400,
|
|
126
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HA1`,
|
|
127
|
-
addECToUserMsg: true,
|
|
128
|
-
message: 'Unsupported response_type',
|
|
129
|
-
userMessage: 'Invalid authorization request',
|
|
130
|
-
issuerService: this.serviceName,
|
|
131
|
-
});
|
|
132
|
-
} catch (error) {
|
|
133
|
-
DyFM_Log.error('Authorization request failed', error);
|
|
134
|
-
throw error;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Validates if the client is registered and active
|
|
140
|
-
* @param clientId The client ID to validate
|
|
141
|
-
* @returns true if the client is valid
|
|
142
|
-
*/
|
|
143
|
-
private isValidClient(clientId: string): boolean {
|
|
144
|
-
const client = this.clients.get(clientId);
|
|
145
|
-
return client?.isActive ?? false;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Validates if the redirect URI is registered for the client
|
|
150
|
-
* @param clientId The client ID
|
|
151
|
-
* @param redirectUri The redirect URI to validate
|
|
152
|
-
* @returns true if the redirect URI is valid
|
|
153
|
-
*/
|
|
154
|
-
private isValidRedirectUri(clientId: string, redirectUri: string): boolean {
|
|
155
|
-
const client = this.clients.get(clientId);
|
|
156
|
-
if (!client) return false;
|
|
157
|
-
|
|
158
|
-
// Check if the redirect URI matches any of the registered URIs
|
|
159
|
-
return client.redirectUris.some(uri => {
|
|
160
|
-
// Simple exact match for now
|
|
161
|
-
// TODO: Implement more sophisticated URI matching (e.g., wildcards, regex)
|
|
162
|
-
return uri === redirectUri;
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Validates if the scope is allowed for the client
|
|
168
|
-
* @param clientId The client ID
|
|
169
|
-
* @param scope The scope to validate
|
|
170
|
-
* @returns true if the scope is valid
|
|
171
|
-
*/
|
|
172
|
-
private isValidScope(clientId: string, scope: string): boolean {
|
|
173
|
-
const client = this.clients.get(clientId);
|
|
174
|
-
if (!client) return false;
|
|
175
|
-
|
|
176
|
-
// If no scope is requested, it's valid
|
|
177
|
-
if (!scope) return true;
|
|
178
|
-
|
|
179
|
-
// Split scope string into individual scopes
|
|
180
|
-
const requestedScopes = scope.split(' ');
|
|
181
|
-
|
|
182
|
-
// Check if all requested scopes are allowed
|
|
183
|
-
return requestedScopes.every(s => client.allowedScopes.includes(s));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Handles the OAuth2 token request
|
|
188
|
-
* @param req Express Request object
|
|
189
|
-
* @param res Express Response object
|
|
190
|
-
*/
|
|
191
|
-
async handleTokenRequest(req: Request, res: Response): Promise<void> {
|
|
192
|
-
try {
|
|
193
|
-
const { grant_type, code, refresh_token, client_id, client_secret, username, password } = req.body;
|
|
194
|
-
|
|
195
|
-
// Validate required parameters
|
|
196
|
-
if (!grant_type || !client_id || !client_secret) {
|
|
197
|
-
throw new DyFM_Error({
|
|
198
|
-
status: 400,
|
|
199
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT0`,
|
|
200
|
-
addECToUserMsg: true,
|
|
201
|
-
message: 'Missing required OAuth2 parameters',
|
|
202
|
-
userMessage: 'Invalid token request',
|
|
203
|
-
issuerService: this.serviceName,
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Validate client credentials
|
|
208
|
-
if (!this.validateClientCredentials(client_id, client_secret)) {
|
|
209
|
-
throw new DyFM_Error({
|
|
210
|
-
status: 401,
|
|
211
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT4`,
|
|
212
|
-
addECToUserMsg: true,
|
|
213
|
-
message: 'Invalid client credentials',
|
|
214
|
-
userMessage: 'Invalid token request',
|
|
215
|
-
issuerService: this.serviceName,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
switch (grant_type) {
|
|
220
|
-
case 'authorization_code':
|
|
221
|
-
if (!code) {
|
|
222
|
-
throw new DyFM_Error({
|
|
223
|
-
status: 400,
|
|
224
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT1`,
|
|
225
|
-
addECToUserMsg: true,
|
|
226
|
-
message: 'Missing authorization code',
|
|
227
|
-
userMessage: 'Invalid token request',
|
|
228
|
-
issuerService: this.serviceName,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Validate authorization code
|
|
233
|
-
const authCodeData = this.authorizationCodes.get(code);
|
|
234
|
-
if (!authCodeData || authCodeData.expiresAt < Date.now()) {
|
|
235
|
-
throw new DyFM_Error({
|
|
236
|
-
status: 400,
|
|
237
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT5`,
|
|
238
|
-
addECToUserMsg: true,
|
|
239
|
-
message: 'Invalid or expired authorization code',
|
|
240
|
-
userMessage: 'Invalid token request',
|
|
241
|
-
issuerService: this.serviceName,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Remove used authorization code
|
|
246
|
-
this.authorizationCodes.delete(code);
|
|
247
|
-
|
|
248
|
-
const accessToken = await this.generateAccessToken(client_id, authCodeData.scope);
|
|
249
|
-
const refreshToken = await this.generateRefreshToken(client_id);
|
|
250
|
-
|
|
251
|
-
// Store refresh token with access token reference
|
|
252
|
-
this.refreshTokens.set(refreshToken, {
|
|
253
|
-
clientId: client_id,
|
|
254
|
-
scope: authCodeData.scope,
|
|
255
|
-
accessToken
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
res.json({
|
|
259
|
-
access_token: accessToken,
|
|
260
|
-
token_type: 'Bearer',
|
|
261
|
-
expires_in: 3600,
|
|
262
|
-
refresh_token: refreshToken,
|
|
263
|
-
scope: authCodeData.scope
|
|
264
|
-
});
|
|
265
|
-
break;
|
|
266
|
-
|
|
267
|
-
case 'refresh_token':
|
|
268
|
-
if (!refresh_token) {
|
|
269
|
-
throw new DyFM_Error({
|
|
270
|
-
status: 400,
|
|
271
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT2`,
|
|
272
|
-
addECToUserMsg: true,
|
|
273
|
-
message: 'Missing refresh token',
|
|
274
|
-
userMessage: 'Invalid token request',
|
|
275
|
-
issuerService: this.serviceName,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Validate refresh token
|
|
280
|
-
const refreshTokenData = this.refreshTokens.get(refresh_token);
|
|
281
|
-
if (!refreshTokenData) {
|
|
282
|
-
throw new DyFM_Error({
|
|
283
|
-
status: 400,
|
|
284
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT6`,
|
|
285
|
-
addECToUserMsg: true,
|
|
286
|
-
message: 'Invalid refresh token',
|
|
287
|
-
userMessage: 'Invalid token request',
|
|
288
|
-
issuerService: this.serviceName,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Revoke old access token
|
|
293
|
-
this.accessTokens.delete(refreshTokenData.accessToken);
|
|
294
|
-
|
|
295
|
-
// Generate new access token
|
|
296
|
-
const newAccessToken = await this.generateAccessToken(client_id, refreshTokenData.scope);
|
|
297
|
-
const newRefreshToken = await this.generateRefreshToken(client_id);
|
|
298
|
-
|
|
299
|
-
// Store new refresh token
|
|
300
|
-
this.refreshTokens.set(newRefreshToken, {
|
|
301
|
-
clientId: client_id,
|
|
302
|
-
scope: refreshTokenData.scope,
|
|
303
|
-
accessToken: newAccessToken
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
res.json({
|
|
307
|
-
access_token: newAccessToken,
|
|
308
|
-
token_type: 'Bearer',
|
|
309
|
-
expires_in: 3600,
|
|
310
|
-
refresh_token: newRefreshToken,
|
|
311
|
-
scope: refreshTokenData.scope
|
|
312
|
-
});
|
|
313
|
-
break;
|
|
314
|
-
|
|
315
|
-
case 'client_credentials':
|
|
316
|
-
const clientAccessToken = await this.generateAccessToken(client_id, '');
|
|
317
|
-
res.json({
|
|
318
|
-
access_token: clientAccessToken,
|
|
319
|
-
token_type: 'Bearer',
|
|
320
|
-
expires_in: 3600
|
|
321
|
-
});
|
|
322
|
-
break;
|
|
323
|
-
|
|
324
|
-
case 'password':
|
|
325
|
-
if (!username || !password) {
|
|
326
|
-
throw new DyFM_Error({
|
|
327
|
-
status: 400,
|
|
328
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT7`,
|
|
329
|
-
addECToUserMsg: true,
|
|
330
|
-
message: 'Missing username or password',
|
|
331
|
-
userMessage: 'Invalid token request',
|
|
332
|
-
issuerService: this.serviceName,
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Authenticate user
|
|
337
|
-
const userScopes = this.authenticateUser(username, password);
|
|
338
|
-
if (!userScopes) {
|
|
339
|
-
throw new DyFM_Error({
|
|
340
|
-
status: 401,
|
|
341
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT8`,
|
|
342
|
-
addECToUserMsg: true,
|
|
343
|
-
message: 'Invalid username or password',
|
|
344
|
-
userMessage: 'Invalid token request',
|
|
345
|
-
issuerService: this.serviceName,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Generate access token
|
|
350
|
-
const userAccessToken = await this.generateAccessToken(client_id, userScopes.join(' '));
|
|
351
|
-
const userRefreshToken = await this.generateRefreshToken(client_id);
|
|
352
|
-
|
|
353
|
-
// Store refresh token with access token reference
|
|
354
|
-
this.refreshTokens.set(userRefreshToken, {
|
|
355
|
-
clientId: client_id,
|
|
356
|
-
scope: userScopes.join(' '),
|
|
357
|
-
accessToken: userAccessToken
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
res.json({
|
|
361
|
-
access_token: userAccessToken,
|
|
362
|
-
token_type: 'Bearer',
|
|
363
|
-
expires_in: 3600,
|
|
364
|
-
refresh_token: userRefreshToken,
|
|
365
|
-
scope: userScopes.join(' ')
|
|
366
|
-
});
|
|
367
|
-
break;
|
|
368
|
-
|
|
369
|
-
default:
|
|
370
|
-
throw new DyFM_Error({
|
|
371
|
-
status: 400,
|
|
372
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT3`,
|
|
373
|
-
addECToUserMsg: true,
|
|
374
|
-
message: 'Unsupported grant_type',
|
|
375
|
-
userMessage: 'Invalid token request',
|
|
376
|
-
issuerService: this.serviceName,
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
} catch (error) {
|
|
380
|
-
DyFM_Log.error('Token request failed', error);
|
|
381
|
-
throw error;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Validates client credentials
|
|
387
|
-
* @param clientId The client ID
|
|
388
|
-
* @param clientSecret The client secret
|
|
389
|
-
* @returns true if the credentials are valid
|
|
390
|
-
*/
|
|
391
|
-
private validateClientCredentials(clientId: string, clientSecret: string): boolean {
|
|
392
|
-
const client = this.clients.get(clientId);
|
|
393
|
-
return (client?.clientSecret === clientSecret) && (client?.isActive ?? false);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Handles the OAuth2 userinfo request
|
|
398
|
-
* @param req Express Request object
|
|
399
|
-
* @param res Express Response object
|
|
400
|
-
*/
|
|
401
|
-
async handleUserInfoRequest(req: Request, res: Response): Promise<void> {
|
|
402
|
-
try {
|
|
403
|
-
const token = this.authService.getTokenFromRequest(req);
|
|
404
|
-
|
|
405
|
-
// Validate token
|
|
406
|
-
const tokenData = this.accessTokens.get(token);
|
|
407
|
-
if (!tokenData || tokenData.expiresAt < Date.now()) {
|
|
408
|
-
throw new DyFM_Error({
|
|
409
|
-
status: 401,
|
|
410
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HU0`,
|
|
411
|
-
addECToUserMsg: true,
|
|
412
|
-
message: 'Invalid or expired access token',
|
|
413
|
-
userMessage: 'Invalid token',
|
|
414
|
-
issuerService: this.serviceName,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Extract user information based on token scope
|
|
419
|
-
const userInfo = await this.getUserInfoFromToken(token);
|
|
420
|
-
|
|
421
|
-
res.json(userInfo);
|
|
422
|
-
} catch (error) {
|
|
423
|
-
DyFM_Log.error('Userinfo request failed', error);
|
|
424
|
-
throw error;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Gets user information from the token
|
|
430
|
-
* @param token The access token
|
|
431
|
-
* @returns The user information object
|
|
432
|
-
*/
|
|
433
|
-
private async getUserInfoFromToken(token: string): Promise<any> {
|
|
434
|
-
const tokenData = this.accessTokens.get(token);
|
|
435
|
-
if (!tokenData) {
|
|
436
|
-
throw new DyFM_Error({
|
|
437
|
-
status: 401,
|
|
438
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HU1`,
|
|
439
|
-
addECToUserMsg: true,
|
|
440
|
-
message: 'Invalid access token',
|
|
441
|
-
userMessage: 'Invalid token',
|
|
442
|
-
issuerService: this.serviceName,
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// TODO: Implement user information retrieval from database/storage
|
|
447
|
-
// For now, return mock user information
|
|
448
|
-
return {
|
|
449
|
-
sub: 'user123',
|
|
450
|
-
name: 'John Doe',
|
|
451
|
-
email: 'john.doe@example.com',
|
|
452
|
-
// Add other user information based on scope
|
|
453
|
-
...(tokenData.scope.includes('profile') && {
|
|
454
|
-
given_name: 'John',
|
|
455
|
-
family_name: 'Doe',
|
|
456
|
-
picture: 'https://example.com/john.jpg'
|
|
457
|
-
}),
|
|
458
|
-
...(tokenData.scope.includes('email') && {
|
|
459
|
-
email_verified: true
|
|
460
|
-
})
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Handles the OAuth2 token revocation request
|
|
466
|
-
* @param req Express Request object
|
|
467
|
-
* @param res Express Response object
|
|
468
|
-
*/
|
|
469
|
-
async handleTokenRevocation(req: Request, res: Response): Promise<void> {
|
|
470
|
-
try {
|
|
471
|
-
const { token, token_type_hint } = req.body;
|
|
472
|
-
|
|
473
|
-
if (!token) {
|
|
474
|
-
throw new DyFM_Error({
|
|
475
|
-
status: 400,
|
|
476
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HR0`,
|
|
477
|
-
addECToUserMsg: true,
|
|
478
|
-
message: 'Missing token',
|
|
479
|
-
userMessage: 'Invalid revocation request',
|
|
480
|
-
issuerService: this.serviceName,
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Try to revoke the token based on token_type_hint
|
|
485
|
-
let revoked = false;
|
|
486
|
-
|
|
487
|
-
if (!token_type_hint || token_type_hint === 'access_token') {
|
|
488
|
-
// Try to revoke as access token
|
|
489
|
-
if (this.accessTokens.delete(token)) {
|
|
490
|
-
revoked = true;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (!revoked && (!token_type_hint || token_type_hint === 'refresh_token')) {
|
|
495
|
-
// Try to revoke as refresh token
|
|
496
|
-
const refreshTokenData = this.refreshTokens.get(token);
|
|
497
|
-
if (refreshTokenData) {
|
|
498
|
-
// Also revoke the associated access token
|
|
499
|
-
this.accessTokens.delete(refreshTokenData.accessToken);
|
|
500
|
-
this.refreshTokens.delete(token);
|
|
501
|
-
revoked = true;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (!revoked) {
|
|
506
|
-
// Token not found or already revoked
|
|
507
|
-
throw new DyFM_Error({
|
|
508
|
-
status: 400,
|
|
509
|
-
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HR1`,
|
|
510
|
-
addECToUserMsg: true,
|
|
511
|
-
message: 'Token not found or already revoked',
|
|
512
|
-
userMessage: 'Invalid revocation request',
|
|
513
|
-
issuerService: this.serviceName,
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
res.status(200).send();
|
|
518
|
-
} catch (error) {
|
|
519
|
-
DyFM_Log.error('Token revocation failed', error);
|
|
520
|
-
throw error;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Generates an authorization code
|
|
526
|
-
* @param clientId The client ID
|
|
527
|
-
* @param scope The requested scope
|
|
528
|
-
* @returns The generated authorization code
|
|
529
|
-
*/
|
|
530
|
-
private async generateAuthorizationCode(clientId: string, scope: string): Promise<string> {
|
|
531
|
-
//const code = randomBytes(32).toString('hex');
|
|
532
|
-
const code = cryptoJs.lib.WordArray.random(32).toString();
|
|
533
|
-
const expiresAt = Date.now() + 600000; // 10 minutes expiration
|
|
534
|
-
|
|
535
|
-
this.authorizationCodes.set(code, {
|
|
536
|
-
clientId,
|
|
537
|
-
scope,
|
|
538
|
-
expiresAt
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
return code;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Generates an access token
|
|
546
|
-
* @param clientId The client ID
|
|
547
|
-
* @param scope The requested scope
|
|
548
|
-
* @returns The generated access token
|
|
549
|
-
*/
|
|
550
|
-
private async generateAccessToken(clientId: string, scope: string): Promise<string> {
|
|
551
|
-
//const token = randomBytes(32).toString('hex');
|
|
552
|
-
const token = cryptoJs.lib.WordArray.random(32).toString();
|
|
553
|
-
const expiresAt = Date.now() + 3600000; // 1 hour expiration
|
|
554
|
-
|
|
555
|
-
this.accessTokens.set(token, {
|
|
556
|
-
clientId,
|
|
557
|
-
scope,
|
|
558
|
-
expiresAt
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
return token;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Generates a refresh token
|
|
566
|
-
* @param clientId The client ID
|
|
567
|
-
* @returns The generated refresh token
|
|
568
|
-
*/
|
|
569
|
-
private async generateRefreshToken(clientId: string): Promise<string> {
|
|
570
|
-
//const token = randomBytes(32).toString('hex');
|
|
571
|
-
const token = cryptoJs.lib.WordArray.random(32).toString();
|
|
572
|
-
|
|
573
|
-
this.refreshTokens.set(token, {
|
|
574
|
-
clientId,
|
|
575
|
-
scope: '',
|
|
576
|
-
accessToken: ''
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
return token;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* Gets the access token data
|
|
584
|
-
* @param token The access token
|
|
585
|
-
* @returns The access token data or undefined if not found
|
|
586
|
-
*/
|
|
587
|
-
getAccessTokenData(token: string): { clientId: string; scope: string; expiresAt: number } | undefined {
|
|
588
|
-
return this.accessTokens.get(token);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* Registers a new OAuth2 client
|
|
593
|
-
* @param clientId The client ID
|
|
594
|
-
* @param clientSecret The client secret
|
|
595
|
-
* @param redirectUris The allowed redirect URIs
|
|
596
|
-
* @param allowedScopes The allowed scopes
|
|
597
|
-
* @returns true if the client was registered successfully
|
|
598
|
-
*/
|
|
599
|
-
registerClient(
|
|
600
|
-
clientId: string,
|
|
601
|
-
clientSecret: string,
|
|
602
|
-
redirectUris: string[],
|
|
603
|
-
allowedScopes: string[]
|
|
604
|
-
): boolean {
|
|
605
|
-
if (this.clients.has(clientId)) {
|
|
606
|
-
return false;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
this.clients.set(clientId, {
|
|
610
|
-
clientId,
|
|
611
|
-
clientSecret,
|
|
612
|
-
redirectUris,
|
|
613
|
-
allowedScopes,
|
|
614
|
-
isActive: true
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
return true;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
/**
|
|
621
|
-
* Authenticates a user with username and password
|
|
622
|
-
* @param username The username
|
|
623
|
-
* @param password The password
|
|
624
|
-
* @returns The user's scopes if authentication is successful, undefined otherwise
|
|
625
|
-
*/
|
|
626
|
-
private authenticateUser(username: string, password: string): string[] | undefined {
|
|
627
|
-
const user = this.users.get(username);
|
|
628
|
-
if (!user || user.password !== password) { // In a real implementation, compare hashed passwords
|
|
629
|
-
return undefined;
|
|
630
|
-
}
|
|
631
|
-
return user.scopes;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Registers a new user
|
|
636
|
-
* @param username The username
|
|
637
|
-
* @param password The password
|
|
638
|
-
* @param scopes The user's scopes
|
|
639
|
-
* @returns true if the user was registered successfully
|
|
640
|
-
*/
|
|
641
|
-
registerUser(username: string, password: string, scopes: string[]): boolean {
|
|
642
|
-
if (this.users.has(username)) {
|
|
643
|
-
return false;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
this.users.set(username, {
|
|
647
|
-
username,
|
|
648
|
-
password, // In a real implementation, hash the password
|
|
649
|
-
scopes
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
return true;
|
|
653
|
-
}
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import { cryptoJs } from 'crypto-js';
|
|
3
|
+
import { DyFM_Error, DyFM_Log } from '@futdevpro/fsm-dynamo';
|
|
4
|
+
import { DyNTS_SingletonService } from '../../../_services/base/singleton.service';
|
|
5
|
+
import { DyNTS_global_settings } from '../../../_collections/global-settings.const';
|
|
6
|
+
import { DyNTS_OAuth2_AuthService } from './oauth2.auth-service';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* OAuth2 Control Service implementation
|
|
10
|
+
*
|
|
11
|
+
* This service handles OAuth2 specific business logic and token management
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const oauth2Service = DyNTS_OAuth2_ControlService.getInstance();
|
|
15
|
+
* await oauth2Service.handleAuthorizationRequest(req, res);
|
|
16
|
+
*/
|
|
17
|
+
export class DyNTS_OAuth2_ControlService extends DyNTS_SingletonService {
|
|
18
|
+
static getInstance(): DyNTS_OAuth2_ControlService {
|
|
19
|
+
return DyNTS_OAuth2_ControlService.getSingletonInstance();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
readonly serviceName: string = 'OAuth2ControlService';
|
|
23
|
+
|
|
24
|
+
private readonly authService: DyNTS_OAuth2_AuthService = DyNTS_OAuth2_AuthService.getInstance();
|
|
25
|
+
private readonly authorizationCodes: Map<string, { clientId: string; scope: string; expiresAt: number }> = new Map();
|
|
26
|
+
private readonly accessTokens: Map<string, { clientId: string; scope: string; expiresAt: number }> = new Map();
|
|
27
|
+
private readonly refreshTokens: Map<string, { clientId: string; scope: string; accessToken: string }> = new Map();
|
|
28
|
+
private readonly clients: Map<string, {
|
|
29
|
+
clientId: string;
|
|
30
|
+
clientSecret: string;
|
|
31
|
+
redirectUris: string[];
|
|
32
|
+
allowedScopes: string[];
|
|
33
|
+
isActive: boolean;
|
|
34
|
+
}> = new Map();
|
|
35
|
+
private readonly users: Map<string, {
|
|
36
|
+
username: string;
|
|
37
|
+
password: string; // In a real implementation, this would be a hashed password
|
|
38
|
+
scopes: string[];
|
|
39
|
+
}> = new Map();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Handles the OAuth2 authorization request
|
|
43
|
+
* @param req Express Request object
|
|
44
|
+
* @param res Express Response object
|
|
45
|
+
*/
|
|
46
|
+
async handleAuthorizationRequest(req: Request, res: Response): Promise<void> {
|
|
47
|
+
try {
|
|
48
|
+
const { response_type, client_id, redirect_uri, scope, state } = req.query;
|
|
49
|
+
|
|
50
|
+
// Validate required parameters
|
|
51
|
+
if (!response_type || !client_id || !redirect_uri) {
|
|
52
|
+
throw new DyFM_Error({
|
|
53
|
+
status: 400,
|
|
54
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HA0`,
|
|
55
|
+
addECToUserMsg: true,
|
|
56
|
+
message: 'Missing required OAuth2 parameters',
|
|
57
|
+
userMessage: 'Invalid authorization request',
|
|
58
|
+
issuerService: this.serviceName,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate client_id against registered clients
|
|
63
|
+
if (!this.isValidClient(client_id as string)) {
|
|
64
|
+
throw new DyFM_Error({
|
|
65
|
+
status: 400,
|
|
66
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HA2`,
|
|
67
|
+
addECToUserMsg: true,
|
|
68
|
+
message: 'Invalid client_id',
|
|
69
|
+
userMessage: 'Invalid authorization request',
|
|
70
|
+
issuerService: this.serviceName,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Validate redirect_uri against registered redirect URIs
|
|
75
|
+
if (!this.isValidRedirectUri(client_id as string, redirect_uri as string)) {
|
|
76
|
+
throw new DyFM_Error({
|
|
77
|
+
status: 400,
|
|
78
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HA3`,
|
|
79
|
+
addECToUserMsg: true,
|
|
80
|
+
message: 'Invalid redirect_uri',
|
|
81
|
+
userMessage: 'Invalid authorization request',
|
|
82
|
+
issuerService: this.serviceName,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Validate scope against allowed scopes
|
|
87
|
+
if (!this.isValidScope(client_id as string, scope as string)) {
|
|
88
|
+
throw new DyFM_Error({
|
|
89
|
+
status: 400,
|
|
90
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HA4`,
|
|
91
|
+
addECToUserMsg: true,
|
|
92
|
+
message: 'Invalid scope',
|
|
93
|
+
userMessage: 'Invalid authorization request',
|
|
94
|
+
issuerService: this.serviceName,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// For authorization code flow
|
|
99
|
+
if (response_type === 'code') {
|
|
100
|
+
const authorizationCode = await this.generateAuthorizationCode(client_id as string, scope as string);
|
|
101
|
+
|
|
102
|
+
// Redirect with authorization code
|
|
103
|
+
const redirectUrl = new URL(redirect_uri as string);
|
|
104
|
+
redirectUrl.searchParams.append('code', authorizationCode);
|
|
105
|
+
if (state) redirectUrl.searchParams.append('state', state as string);
|
|
106
|
+
|
|
107
|
+
res.redirect(redirectUrl.toString());
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// For implicit flow
|
|
112
|
+
if (response_type === 'token') {
|
|
113
|
+
const accessToken = await this.generateAccessToken(client_id as string, scope as string);
|
|
114
|
+
|
|
115
|
+
// Redirect with access token
|
|
116
|
+
const redirectUrl = new URL(redirect_uri as string);
|
|
117
|
+
redirectUrl.hash = `access_token=${accessToken}`;
|
|
118
|
+
if (state) redirectUrl.hash += `&state=${state}`;
|
|
119
|
+
|
|
120
|
+
res.redirect(redirectUrl.toString());
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new DyFM_Error({
|
|
125
|
+
status: 400,
|
|
126
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HA1`,
|
|
127
|
+
addECToUserMsg: true,
|
|
128
|
+
message: 'Unsupported response_type',
|
|
129
|
+
userMessage: 'Invalid authorization request',
|
|
130
|
+
issuerService: this.serviceName,
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
DyFM_Log.error('Authorization request failed', error);
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Validates if the client is registered and active
|
|
140
|
+
* @param clientId The client ID to validate
|
|
141
|
+
* @returns true if the client is valid
|
|
142
|
+
*/
|
|
143
|
+
private isValidClient(clientId: string): boolean {
|
|
144
|
+
const client = this.clients.get(clientId);
|
|
145
|
+
return client?.isActive ?? false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validates if the redirect URI is registered for the client
|
|
150
|
+
* @param clientId The client ID
|
|
151
|
+
* @param redirectUri The redirect URI to validate
|
|
152
|
+
* @returns true if the redirect URI is valid
|
|
153
|
+
*/
|
|
154
|
+
private isValidRedirectUri(clientId: string, redirectUri: string): boolean {
|
|
155
|
+
const client = this.clients.get(clientId);
|
|
156
|
+
if (!client) return false;
|
|
157
|
+
|
|
158
|
+
// Check if the redirect URI matches any of the registered URIs
|
|
159
|
+
return client.redirectUris.some(uri => {
|
|
160
|
+
// Simple exact match for now
|
|
161
|
+
// TODO: Implement more sophisticated URI matching (e.g., wildcards, regex)
|
|
162
|
+
return uri === redirectUri;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Validates if the scope is allowed for the client
|
|
168
|
+
* @param clientId The client ID
|
|
169
|
+
* @param scope The scope to validate
|
|
170
|
+
* @returns true if the scope is valid
|
|
171
|
+
*/
|
|
172
|
+
private isValidScope(clientId: string, scope: string): boolean {
|
|
173
|
+
const client = this.clients.get(clientId);
|
|
174
|
+
if (!client) return false;
|
|
175
|
+
|
|
176
|
+
// If no scope is requested, it's valid
|
|
177
|
+
if (!scope) return true;
|
|
178
|
+
|
|
179
|
+
// Split scope string into individual scopes
|
|
180
|
+
const requestedScopes = scope.split(' ');
|
|
181
|
+
|
|
182
|
+
// Check if all requested scopes are allowed
|
|
183
|
+
return requestedScopes.every(s => client.allowedScopes.includes(s));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Handles the OAuth2 token request
|
|
188
|
+
* @param req Express Request object
|
|
189
|
+
* @param res Express Response object
|
|
190
|
+
*/
|
|
191
|
+
async handleTokenRequest(req: Request, res: Response): Promise<void> {
|
|
192
|
+
try {
|
|
193
|
+
const { grant_type, code, refresh_token, client_id, client_secret, username, password } = req.body;
|
|
194
|
+
|
|
195
|
+
// Validate required parameters
|
|
196
|
+
if (!grant_type || !client_id || !client_secret) {
|
|
197
|
+
throw new DyFM_Error({
|
|
198
|
+
status: 400,
|
|
199
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT0`,
|
|
200
|
+
addECToUserMsg: true,
|
|
201
|
+
message: 'Missing required OAuth2 parameters',
|
|
202
|
+
userMessage: 'Invalid token request',
|
|
203
|
+
issuerService: this.serviceName,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Validate client credentials
|
|
208
|
+
if (!this.validateClientCredentials(client_id, client_secret)) {
|
|
209
|
+
throw new DyFM_Error({
|
|
210
|
+
status: 401,
|
|
211
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT4`,
|
|
212
|
+
addECToUserMsg: true,
|
|
213
|
+
message: 'Invalid client credentials',
|
|
214
|
+
userMessage: 'Invalid token request',
|
|
215
|
+
issuerService: this.serviceName,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
switch (grant_type) {
|
|
220
|
+
case 'authorization_code':
|
|
221
|
+
if (!code) {
|
|
222
|
+
throw new DyFM_Error({
|
|
223
|
+
status: 400,
|
|
224
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT1`,
|
|
225
|
+
addECToUserMsg: true,
|
|
226
|
+
message: 'Missing authorization code',
|
|
227
|
+
userMessage: 'Invalid token request',
|
|
228
|
+
issuerService: this.serviceName,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Validate authorization code
|
|
233
|
+
const authCodeData = this.authorizationCodes.get(code);
|
|
234
|
+
if (!authCodeData || authCodeData.expiresAt < Date.now()) {
|
|
235
|
+
throw new DyFM_Error({
|
|
236
|
+
status: 400,
|
|
237
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT5`,
|
|
238
|
+
addECToUserMsg: true,
|
|
239
|
+
message: 'Invalid or expired authorization code',
|
|
240
|
+
userMessage: 'Invalid token request',
|
|
241
|
+
issuerService: this.serviceName,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Remove used authorization code
|
|
246
|
+
this.authorizationCodes.delete(code);
|
|
247
|
+
|
|
248
|
+
const accessToken = await this.generateAccessToken(client_id, authCodeData.scope);
|
|
249
|
+
const refreshToken = await this.generateRefreshToken(client_id);
|
|
250
|
+
|
|
251
|
+
// Store refresh token with access token reference
|
|
252
|
+
this.refreshTokens.set(refreshToken, {
|
|
253
|
+
clientId: client_id,
|
|
254
|
+
scope: authCodeData.scope,
|
|
255
|
+
accessToken
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
res.json({
|
|
259
|
+
access_token: accessToken,
|
|
260
|
+
token_type: 'Bearer',
|
|
261
|
+
expires_in: 3600,
|
|
262
|
+
refresh_token: refreshToken,
|
|
263
|
+
scope: authCodeData.scope
|
|
264
|
+
});
|
|
265
|
+
break;
|
|
266
|
+
|
|
267
|
+
case 'refresh_token':
|
|
268
|
+
if (!refresh_token) {
|
|
269
|
+
throw new DyFM_Error({
|
|
270
|
+
status: 400,
|
|
271
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT2`,
|
|
272
|
+
addECToUserMsg: true,
|
|
273
|
+
message: 'Missing refresh token',
|
|
274
|
+
userMessage: 'Invalid token request',
|
|
275
|
+
issuerService: this.serviceName,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Validate refresh token
|
|
280
|
+
const refreshTokenData = this.refreshTokens.get(refresh_token);
|
|
281
|
+
if (!refreshTokenData) {
|
|
282
|
+
throw new DyFM_Error({
|
|
283
|
+
status: 400,
|
|
284
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT6`,
|
|
285
|
+
addECToUserMsg: true,
|
|
286
|
+
message: 'Invalid refresh token',
|
|
287
|
+
userMessage: 'Invalid token request',
|
|
288
|
+
issuerService: this.serviceName,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Revoke old access token
|
|
293
|
+
this.accessTokens.delete(refreshTokenData.accessToken);
|
|
294
|
+
|
|
295
|
+
// Generate new access token
|
|
296
|
+
const newAccessToken = await this.generateAccessToken(client_id, refreshTokenData.scope);
|
|
297
|
+
const newRefreshToken = await this.generateRefreshToken(client_id);
|
|
298
|
+
|
|
299
|
+
// Store new refresh token
|
|
300
|
+
this.refreshTokens.set(newRefreshToken, {
|
|
301
|
+
clientId: client_id,
|
|
302
|
+
scope: refreshTokenData.scope,
|
|
303
|
+
accessToken: newAccessToken
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
res.json({
|
|
307
|
+
access_token: newAccessToken,
|
|
308
|
+
token_type: 'Bearer',
|
|
309
|
+
expires_in: 3600,
|
|
310
|
+
refresh_token: newRefreshToken,
|
|
311
|
+
scope: refreshTokenData.scope
|
|
312
|
+
});
|
|
313
|
+
break;
|
|
314
|
+
|
|
315
|
+
case 'client_credentials':
|
|
316
|
+
const clientAccessToken = await this.generateAccessToken(client_id, '');
|
|
317
|
+
res.json({
|
|
318
|
+
access_token: clientAccessToken,
|
|
319
|
+
token_type: 'Bearer',
|
|
320
|
+
expires_in: 3600
|
|
321
|
+
});
|
|
322
|
+
break;
|
|
323
|
+
|
|
324
|
+
case 'password':
|
|
325
|
+
if (!username || !password) {
|
|
326
|
+
throw new DyFM_Error({
|
|
327
|
+
status: 400,
|
|
328
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT7`,
|
|
329
|
+
addECToUserMsg: true,
|
|
330
|
+
message: 'Missing username or password',
|
|
331
|
+
userMessage: 'Invalid token request',
|
|
332
|
+
issuerService: this.serviceName,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Authenticate user
|
|
337
|
+
const userScopes = this.authenticateUser(username, password);
|
|
338
|
+
if (!userScopes) {
|
|
339
|
+
throw new DyFM_Error({
|
|
340
|
+
status: 401,
|
|
341
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT8`,
|
|
342
|
+
addECToUserMsg: true,
|
|
343
|
+
message: 'Invalid username or password',
|
|
344
|
+
userMessage: 'Invalid token request',
|
|
345
|
+
issuerService: this.serviceName,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Generate access token
|
|
350
|
+
const userAccessToken = await this.generateAccessToken(client_id, userScopes.join(' '));
|
|
351
|
+
const userRefreshToken = await this.generateRefreshToken(client_id);
|
|
352
|
+
|
|
353
|
+
// Store refresh token with access token reference
|
|
354
|
+
this.refreshTokens.set(userRefreshToken, {
|
|
355
|
+
clientId: client_id,
|
|
356
|
+
scope: userScopes.join(' '),
|
|
357
|
+
accessToken: userAccessToken
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
res.json({
|
|
361
|
+
access_token: userAccessToken,
|
|
362
|
+
token_type: 'Bearer',
|
|
363
|
+
expires_in: 3600,
|
|
364
|
+
refresh_token: userRefreshToken,
|
|
365
|
+
scope: userScopes.join(' ')
|
|
366
|
+
});
|
|
367
|
+
break;
|
|
368
|
+
|
|
369
|
+
default:
|
|
370
|
+
throw new DyFM_Error({
|
|
371
|
+
status: 400,
|
|
372
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HT3`,
|
|
373
|
+
addECToUserMsg: true,
|
|
374
|
+
message: 'Unsupported grant_type',
|
|
375
|
+
userMessage: 'Invalid token request',
|
|
376
|
+
issuerService: this.serviceName,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
DyFM_Log.error('Token request failed', error);
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Validates client credentials
|
|
387
|
+
* @param clientId The client ID
|
|
388
|
+
* @param clientSecret The client secret
|
|
389
|
+
* @returns true if the credentials are valid
|
|
390
|
+
*/
|
|
391
|
+
private validateClientCredentials(clientId: string, clientSecret: string): boolean {
|
|
392
|
+
const client = this.clients.get(clientId);
|
|
393
|
+
return (client?.clientSecret === clientSecret) && (client?.isActive ?? false);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Handles the OAuth2 userinfo request
|
|
398
|
+
* @param req Express Request object
|
|
399
|
+
* @param res Express Response object
|
|
400
|
+
*/
|
|
401
|
+
async handleUserInfoRequest(req: Request, res: Response): Promise<void> {
|
|
402
|
+
try {
|
|
403
|
+
const token = this.authService.getTokenFromRequest(req);
|
|
404
|
+
|
|
405
|
+
// Validate token
|
|
406
|
+
const tokenData = this.accessTokens.get(token);
|
|
407
|
+
if (!tokenData || tokenData.expiresAt < Date.now()) {
|
|
408
|
+
throw new DyFM_Error({
|
|
409
|
+
status: 401,
|
|
410
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HU0`,
|
|
411
|
+
addECToUserMsg: true,
|
|
412
|
+
message: 'Invalid or expired access token',
|
|
413
|
+
userMessage: 'Invalid token',
|
|
414
|
+
issuerService: this.serviceName,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Extract user information based on token scope
|
|
419
|
+
const userInfo = await this.getUserInfoFromToken(token);
|
|
420
|
+
|
|
421
|
+
res.json(userInfo);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
DyFM_Log.error('Userinfo request failed', error);
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Gets user information from the token
|
|
430
|
+
* @param token The access token
|
|
431
|
+
* @returns The user information object
|
|
432
|
+
*/
|
|
433
|
+
private async getUserInfoFromToken(token: string): Promise<any> {
|
|
434
|
+
const tokenData = this.accessTokens.get(token);
|
|
435
|
+
if (!tokenData) {
|
|
436
|
+
throw new DyFM_Error({
|
|
437
|
+
status: 401,
|
|
438
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HU1`,
|
|
439
|
+
addECToUserMsg: true,
|
|
440
|
+
message: 'Invalid access token',
|
|
441
|
+
userMessage: 'Invalid token',
|
|
442
|
+
issuerService: this.serviceName,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// TODO: Implement user information retrieval from database/storage
|
|
447
|
+
// For now, return mock user information
|
|
448
|
+
return {
|
|
449
|
+
sub: 'user123',
|
|
450
|
+
name: 'John Doe',
|
|
451
|
+
email: 'john.doe@example.com',
|
|
452
|
+
// Add other user information based on scope
|
|
453
|
+
...(tokenData.scope.includes('profile') && {
|
|
454
|
+
given_name: 'John',
|
|
455
|
+
family_name: 'Doe',
|
|
456
|
+
picture: 'https://example.com/john.jpg'
|
|
457
|
+
}),
|
|
458
|
+
...(tokenData.scope.includes('email') && {
|
|
459
|
+
email_verified: true
|
|
460
|
+
})
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Handles the OAuth2 token revocation request
|
|
466
|
+
* @param req Express Request object
|
|
467
|
+
* @param res Express Response object
|
|
468
|
+
*/
|
|
469
|
+
async handleTokenRevocation(req: Request, res: Response): Promise<void> {
|
|
470
|
+
try {
|
|
471
|
+
const { token, token_type_hint } = req.body;
|
|
472
|
+
|
|
473
|
+
if (!token) {
|
|
474
|
+
throw new DyFM_Error({
|
|
475
|
+
status: 400,
|
|
476
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HR0`,
|
|
477
|
+
addECToUserMsg: true,
|
|
478
|
+
message: 'Missing token',
|
|
479
|
+
userMessage: 'Invalid revocation request',
|
|
480
|
+
issuerService: this.serviceName,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Try to revoke the token based on token_type_hint
|
|
485
|
+
let revoked = false;
|
|
486
|
+
|
|
487
|
+
if (!token_type_hint || token_type_hint === 'access_token') {
|
|
488
|
+
// Try to revoke as access token
|
|
489
|
+
if (this.accessTokens.delete(token)) {
|
|
490
|
+
revoked = true;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (!revoked && (!token_type_hint || token_type_hint === 'refresh_token')) {
|
|
495
|
+
// Try to revoke as refresh token
|
|
496
|
+
const refreshTokenData = this.refreshTokens.get(token);
|
|
497
|
+
if (refreshTokenData) {
|
|
498
|
+
// Also revoke the associated access token
|
|
499
|
+
this.accessTokens.delete(refreshTokenData.accessToken);
|
|
500
|
+
this.refreshTokens.delete(token);
|
|
501
|
+
revoked = true;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (!revoked) {
|
|
506
|
+
// Token not found or already revoked
|
|
507
|
+
throw new DyFM_Error({
|
|
508
|
+
status: 400,
|
|
509
|
+
errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-OA2-HR1`,
|
|
510
|
+
addECToUserMsg: true,
|
|
511
|
+
message: 'Token not found or already revoked',
|
|
512
|
+
userMessage: 'Invalid revocation request',
|
|
513
|
+
issuerService: this.serviceName,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
res.status(200).send();
|
|
518
|
+
} catch (error) {
|
|
519
|
+
DyFM_Log.error('Token revocation failed', error);
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Generates an authorization code
|
|
526
|
+
* @param clientId The client ID
|
|
527
|
+
* @param scope The requested scope
|
|
528
|
+
* @returns The generated authorization code
|
|
529
|
+
*/
|
|
530
|
+
private async generateAuthorizationCode(clientId: string, scope: string): Promise<string> {
|
|
531
|
+
//const code = randomBytes(32).toString('hex');
|
|
532
|
+
const code = cryptoJs.lib.WordArray.random(32).toString();
|
|
533
|
+
const expiresAt = Date.now() + 600000; // 10 minutes expiration
|
|
534
|
+
|
|
535
|
+
this.authorizationCodes.set(code, {
|
|
536
|
+
clientId,
|
|
537
|
+
scope,
|
|
538
|
+
expiresAt
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
return code;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Generates an access token
|
|
546
|
+
* @param clientId The client ID
|
|
547
|
+
* @param scope The requested scope
|
|
548
|
+
* @returns The generated access token
|
|
549
|
+
*/
|
|
550
|
+
private async generateAccessToken(clientId: string, scope: string): Promise<string> {
|
|
551
|
+
//const token = randomBytes(32).toString('hex');
|
|
552
|
+
const token = cryptoJs.lib.WordArray.random(32).toString();
|
|
553
|
+
const expiresAt = Date.now() + 3600000; // 1 hour expiration
|
|
554
|
+
|
|
555
|
+
this.accessTokens.set(token, {
|
|
556
|
+
clientId,
|
|
557
|
+
scope,
|
|
558
|
+
expiresAt
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
return token;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Generates a refresh token
|
|
566
|
+
* @param clientId The client ID
|
|
567
|
+
* @returns The generated refresh token
|
|
568
|
+
*/
|
|
569
|
+
private async generateRefreshToken(clientId: string): Promise<string> {
|
|
570
|
+
//const token = randomBytes(32).toString('hex');
|
|
571
|
+
const token = cryptoJs.lib.WordArray.random(32).toString();
|
|
572
|
+
|
|
573
|
+
this.refreshTokens.set(token, {
|
|
574
|
+
clientId,
|
|
575
|
+
scope: '',
|
|
576
|
+
accessToken: ''
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
return token;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Gets the access token data
|
|
584
|
+
* @param token The access token
|
|
585
|
+
* @returns The access token data or undefined if not found
|
|
586
|
+
*/
|
|
587
|
+
getAccessTokenData(token: string): { clientId: string; scope: string; expiresAt: number } | undefined {
|
|
588
|
+
return this.accessTokens.get(token);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Registers a new OAuth2 client
|
|
593
|
+
* @param clientId The client ID
|
|
594
|
+
* @param clientSecret The client secret
|
|
595
|
+
* @param redirectUris The allowed redirect URIs
|
|
596
|
+
* @param allowedScopes The allowed scopes
|
|
597
|
+
* @returns true if the client was registered successfully
|
|
598
|
+
*/
|
|
599
|
+
registerClient(
|
|
600
|
+
clientId: string,
|
|
601
|
+
clientSecret: string,
|
|
602
|
+
redirectUris: string[],
|
|
603
|
+
allowedScopes: string[]
|
|
604
|
+
): boolean {
|
|
605
|
+
if (this.clients.has(clientId)) {
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
this.clients.set(clientId, {
|
|
610
|
+
clientId,
|
|
611
|
+
clientSecret,
|
|
612
|
+
redirectUris,
|
|
613
|
+
allowedScopes,
|
|
614
|
+
isActive: true
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Authenticates a user with username and password
|
|
622
|
+
* @param username The username
|
|
623
|
+
* @param password The password
|
|
624
|
+
* @returns The user's scopes if authentication is successful, undefined otherwise
|
|
625
|
+
*/
|
|
626
|
+
private authenticateUser(username: string, password: string): string[] | undefined {
|
|
627
|
+
const user = this.users.get(username);
|
|
628
|
+
if (!user || user.password !== password) { // In a real implementation, compare hashed passwords
|
|
629
|
+
return undefined;
|
|
630
|
+
}
|
|
631
|
+
return user.scopes;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Registers a new user
|
|
636
|
+
* @param username The username
|
|
637
|
+
* @param password The password
|
|
638
|
+
* @param scopes The user's scopes
|
|
639
|
+
* @returns true if the user was registered successfully
|
|
640
|
+
*/
|
|
641
|
+
registerUser(username: string, password: string, scopes: string[]): boolean {
|
|
642
|
+
if (this.users.has(username)) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
this.users.set(username, {
|
|
647
|
+
username,
|
|
648
|
+
password, // In a real implementation, hash the password
|
|
649
|
+
scopes
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
654
|
}
|