@egain/egain-mcp-server 1.0.1
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/.speakeasy/templates/src/lib/env.ts +1 -0
- package/README.md +119 -0
- package/bin/mcp-server.js +47817 -0
- package/bin/mcp-server.js.map +371 -0
- package/esm/src/cloudflare-worker/cloudflare-worker.d.ts +17 -0
- package/esm/src/cloudflare-worker/cloudflare-worker.d.ts.map +1 -0
- package/esm/src/cloudflare-worker/cloudflare-worker.js +72 -0
- package/esm/src/cloudflare-worker/cloudflare-worker.js.map +1 -0
- package/esm/src/cloudflare-worker/landing-page.d.ts +2 -0
- package/esm/src/cloudflare-worker/landing-page.d.ts.map +1 -0
- package/esm/src/cloudflare-worker/landing-page.js +330 -0
- package/esm/src/cloudflare-worker/landing-page.js.map +1 -0
- package/esm/src/core.d.ts +10 -0
- package/esm/src/core.d.ts.map +1 -0
- package/esm/src/core.js +13 -0
- package/esm/src/core.js.map +1 -0
- package/esm/src/funcs/getAnnouncements.d.ts +25 -0
- package/esm/src/funcs/getAnnouncements.d.ts.map +1 -0
- package/esm/src/funcs/getAnnouncements.js +111 -0
- package/esm/src/funcs/getAnnouncements.js.map +1 -0
- package/esm/src/funcs/getAnswers.d.ts +25 -0
- package/esm/src/funcs/getAnswers.d.ts.map +1 -0
- package/esm/src/funcs/getAnswers.js +115 -0
- package/esm/src/funcs/getAnswers.js.map +1 -0
- package/esm/src/funcs/getArticle.d.ts +28 -0
- package/esm/src/funcs/getArticle.d.ts.map +1 -0
- package/esm/src/funcs/getArticle.js +118 -0
- package/esm/src/funcs/getArticle.js.map +1 -0
- package/esm/src/funcs/getPopularArticles.d.ts +25 -0
- package/esm/src/funcs/getPopularArticles.d.ts.map +1 -0
- package/esm/src/funcs/getPopularArticles.js +111 -0
- package/esm/src/funcs/getPopularArticles.js.map +1 -0
- package/esm/src/funcs/getPortals.d.ts +23 -0
- package/esm/src/funcs/getPortals.d.ts.map +1 -0
- package/esm/src/funcs/getPortals.js +106 -0
- package/esm/src/funcs/getPortals.js.map +1 -0
- package/esm/src/funcs/makeSuggestion.d.ts +25 -0
- package/esm/src/funcs/makeSuggestion.d.ts.map +1 -0
- package/esm/src/funcs/makeSuggestion.js +104 -0
- package/esm/src/funcs/makeSuggestion.js.map +1 -0
- package/esm/src/funcs/queryAnswers.d.ts +25 -0
- package/esm/src/funcs/queryAnswers.d.ts.map +1 -0
- package/esm/src/funcs/queryAnswers.js +109 -0
- package/esm/src/funcs/queryAnswers.js.map +1 -0
- package/esm/src/funcs/queryRetrieve.d.ts +27 -0
- package/esm/src/funcs/queryRetrieve.d.ts.map +1 -0
- package/esm/src/funcs/queryRetrieve.js +115 -0
- package/esm/src/funcs/queryRetrieve.js.map +1 -0
- package/esm/src/funcs/querySearch.d.ts +16 -0
- package/esm/src/funcs/querySearch.d.ts.map +1 -0
- package/esm/src/funcs/querySearch.js +98 -0
- package/esm/src/funcs/querySearch.js.map +1 -0
- package/esm/src/funcs/retrieveChunks.d.ts +27 -0
- package/esm/src/funcs/retrieveChunks.d.ts.map +1 -0
- package/esm/src/funcs/retrieveChunks.js +117 -0
- package/esm/src/funcs/retrieveChunks.js.map +1 -0
- package/esm/src/funcs/searchAiSearch.d.ts +16 -0
- package/esm/src/funcs/searchAiSearch.d.ts.map +1 -0
- package/esm/src/funcs/searchAiSearch.js +102 -0
- package/esm/src/funcs/searchAiSearch.js.map +1 -0
- package/esm/src/hooks/auth-hook.d.ts +100 -0
- package/esm/src/hooks/auth-hook.d.ts.map +1 -0
- package/esm/src/hooks/auth-hook.js +1565 -0
- package/esm/src/hooks/auth-hook.js.map +1 -0
- package/esm/src/hooks/hooks.d.ts +25 -0
- package/esm/src/hooks/hooks.d.ts.map +1 -0
- package/esm/src/hooks/hooks.js +82 -0
- package/esm/src/hooks/hooks.js.map +1 -0
- package/esm/src/hooks/portal-cache-hook.d.ts +34 -0
- package/esm/src/hooks/portal-cache-hook.d.ts.map +1 -0
- package/esm/src/hooks/portal-cache-hook.js +441 -0
- package/esm/src/hooks/portal-cache-hook.js.map +1 -0
- package/esm/src/hooks/portal-lookup-hook.d.ts +17 -0
- package/esm/src/hooks/portal-lookup-hook.d.ts.map +1 -0
- package/esm/src/hooks/portal-lookup-hook.js +374 -0
- package/esm/src/hooks/portal-lookup-hook.js.map +1 -0
- package/esm/src/hooks/registration.d.ts +3 -0
- package/esm/src/hooks/registration.d.ts.map +1 -0
- package/esm/src/hooks/registration.js +36 -0
- package/esm/src/hooks/registration.js.map +1 -0
- package/esm/src/hooks/server-routing-hook.d.ts +19 -0
- package/esm/src/hooks/server-routing-hook.d.ts.map +1 -0
- package/esm/src/hooks/server-routing-hook.js +43 -0
- package/esm/src/hooks/server-routing-hook.js.map +1 -0
- package/esm/src/hooks/types.d.ts +76 -0
- package/esm/src/hooks/types.d.ts.map +1 -0
- package/esm/src/hooks/types.js +5 -0
- package/esm/src/hooks/types.js.map +1 -0
- package/esm/src/lib/base64.d.ts +10 -0
- package/esm/src/lib/base64.d.ts.map +1 -0
- package/esm/src/lib/base64.js +29 -0
- package/esm/src/lib/base64.js.map +1 -0
- package/esm/src/lib/config.d.ts +57 -0
- package/esm/src/lib/config.d.ts.map +1 -0
- package/esm/src/lib/config.js +46 -0
- package/esm/src/lib/config.js.map +1 -0
- package/esm/src/lib/dlv.d.ts +14 -0
- package/esm/src/lib/dlv.d.ts.map +1 -0
- package/esm/src/lib/dlv.js +46 -0
- package/esm/src/lib/dlv.js.map +1 -0
- package/esm/src/lib/encodings.d.ts +52 -0
- package/esm/src/lib/encodings.d.ts.map +1 -0
- package/esm/src/lib/encodings.js +354 -0
- package/esm/src/lib/encodings.js.map +1 -0
- package/esm/src/lib/env.d.ts +15 -0
- package/esm/src/lib/env.d.ts.map +1 -0
- package/esm/src/lib/env.js +27 -0
- package/esm/src/lib/env.js.map +1 -0
- package/esm/src/lib/files.d.ts +13 -0
- package/esm/src/lib/files.d.ts.map +1 -0
- package/esm/src/lib/files.js +73 -0
- package/esm/src/lib/files.js.map +1 -0
- package/esm/src/lib/http.d.ts +67 -0
- package/esm/src/lib/http.d.ts.map +1 -0
- package/esm/src/lib/http.js +209 -0
- package/esm/src/lib/http.js.map +1 -0
- package/esm/src/lib/is-plain-object.d.ts +2 -0
- package/esm/src/lib/is-plain-object.d.ts.map +1 -0
- package/esm/src/lib/is-plain-object.js +38 -0
- package/esm/src/lib/is-plain-object.js.map +1 -0
- package/esm/src/lib/logger.d.ts +6 -0
- package/esm/src/lib/logger.d.ts.map +1 -0
- package/esm/src/lib/logger.js +5 -0
- package/esm/src/lib/logger.js.map +1 -0
- package/esm/src/lib/matchers.d.ts +66 -0
- package/esm/src/lib/matchers.d.ts.map +1 -0
- package/esm/src/lib/matchers.js +209 -0
- package/esm/src/lib/matchers.js.map +1 -0
- package/esm/src/lib/primitives.d.ts +11 -0
- package/esm/src/lib/primitives.d.ts.map +1 -0
- package/esm/src/lib/primitives.js +81 -0
- package/esm/src/lib/primitives.js.map +1 -0
- package/esm/src/lib/result.d.ts +31 -0
- package/esm/src/lib/result.d.ts.map +1 -0
- package/esm/src/lib/result.js +31 -0
- package/esm/src/lib/result.js.map +1 -0
- package/esm/src/lib/retries.d.ts +38 -0
- package/esm/src/lib/retries.d.ts.map +1 -0
- package/esm/src/lib/retries.js +150 -0
- package/esm/src/lib/retries.js.map +1 -0
- package/esm/src/lib/schemas.d.ts +19 -0
- package/esm/src/lib/schemas.d.ts.map +1 -0
- package/esm/src/lib/schemas.js +57 -0
- package/esm/src/lib/schemas.js.map +1 -0
- package/esm/src/lib/sdks.d.ts +63 -0
- package/esm/src/lib/sdks.d.ts.map +1 -0
- package/esm/src/lib/sdks.js +258 -0
- package/esm/src/lib/sdks.js.map +1 -0
- package/esm/src/lib/security.d.ts +83 -0
- package/esm/src/lib/security.d.ts.map +1 -0
- package/esm/src/lib/security.js +138 -0
- package/esm/src/lib/security.js.map +1 -0
- package/esm/src/lib/url.d.ts +5 -0
- package/esm/src/lib/url.d.ts.map +1 -0
- package/esm/src/lib/url.js +22 -0
- package/esm/src/lib/url.js.map +1 -0
- package/esm/src/mcp-server/cli/start/command.d.ts +2 -0
- package/esm/src/mcp-server/cli/start/command.d.ts.map +1 -0
- package/esm/src/mcp-server/cli/start/command.js +96 -0
- package/esm/src/mcp-server/cli/start/command.js.map +1 -0
- package/esm/src/mcp-server/cli/start/impl.d.ts +17 -0
- package/esm/src/mcp-server/cli/start/impl.d.ts.map +1 -0
- package/esm/src/mcp-server/cli/start/impl.js +98 -0
- package/esm/src/mcp-server/cli/start/impl.js.map +1 -0
- package/esm/src/mcp-server/cli.d.ts +6 -0
- package/esm/src/mcp-server/cli.d.ts.map +1 -0
- package/esm/src/mcp-server/cli.js +7 -0
- package/esm/src/mcp-server/cli.js.map +1 -0
- package/esm/src/mcp-server/console-logger.d.ts +9 -0
- package/esm/src/mcp-server/console-logger.d.ts.map +1 -0
- package/esm/src/mcp-server/console-logger.js +56 -0
- package/esm/src/mcp-server/console-logger.js.map +1 -0
- package/esm/src/mcp-server/extensions.d.ts +11 -0
- package/esm/src/mcp-server/extensions.d.ts.map +1 -0
- package/esm/src/mcp-server/extensions.js +5 -0
- package/esm/src/mcp-server/extensions.js.map +1 -0
- package/esm/src/mcp-server/mcp-server.d.ts +2 -0
- package/esm/src/mcp-server/mcp-server.d.ts.map +1 -0
- package/esm/src/mcp-server/mcp-server.js +23 -0
- package/esm/src/mcp-server/mcp-server.js.map +1 -0
- package/esm/src/mcp-server/prompts.d.ts +26 -0
- package/esm/src/mcp-server/prompts.d.ts.map +1 -0
- package/esm/src/mcp-server/prompts.js +47 -0
- package/esm/src/mcp-server/prompts.js.map +1 -0
- package/esm/src/mcp-server/resources.d.ts +32 -0
- package/esm/src/mcp-server/resources.d.ts.map +1 -0
- package/esm/src/mcp-server/resources.js +82 -0
- package/esm/src/mcp-server/resources.js.map +1 -0
- package/esm/src/mcp-server/scopes.d.ts +3 -0
- package/esm/src/mcp-server/scopes.d.ts.map +1 -0
- package/esm/src/mcp-server/scopes.js +5 -0
- package/esm/src/mcp-server/scopes.js.map +1 -0
- package/esm/src/mcp-server/server.d.ts +16 -0
- package/esm/src/mcp-server/server.d.ts.map +1 -0
- package/esm/src/mcp-server/server.js +53 -0
- package/esm/src/mcp-server/server.js.map +1 -0
- package/esm/src/mcp-server/shared.d.ts +8 -0
- package/esm/src/mcp-server/shared.d.ts.map +1 -0
- package/esm/src/mcp-server/shared.js +61 -0
- package/esm/src/mcp-server/shared.js.map +1 -0
- package/esm/src/mcp-server/tools/getAnnouncements.d.ts +7 -0
- package/esm/src/mcp-server/tools/getAnnouncements.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/getAnnouncements.js +44 -0
- package/esm/src/mcp-server/tools/getAnnouncements.js.map +1 -0
- package/esm/src/mcp-server/tools/getAnswers.d.ts +7 -0
- package/esm/src/mcp-server/tools/getAnswers.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/getAnswers.js +44 -0
- package/esm/src/mcp-server/tools/getAnswers.js.map +1 -0
- package/esm/src/mcp-server/tools/getArticle.d.ts +7 -0
- package/esm/src/mcp-server/tools/getArticle.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/getArticle.js +47 -0
- package/esm/src/mcp-server/tools/getArticle.js.map +1 -0
- package/esm/src/mcp-server/tools/getPopularArticles.d.ts +7 -0
- package/esm/src/mcp-server/tools/getPopularArticles.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/getPopularArticles.js +44 -0
- package/esm/src/mcp-server/tools/getPopularArticles.js.map +1 -0
- package/esm/src/mcp-server/tools/getPortals.d.ts +7 -0
- package/esm/src/mcp-server/tools/getPortals.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/getPortals.js +42 -0
- package/esm/src/mcp-server/tools/getPortals.js.map +1 -0
- package/esm/src/mcp-server/tools/makeSuggestion.d.ts +7 -0
- package/esm/src/mcp-server/tools/makeSuggestion.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/makeSuggestion.js +44 -0
- package/esm/src/mcp-server/tools/makeSuggestion.js.map +1 -0
- package/esm/src/mcp-server/tools/queryAnswers.d.ts +7 -0
- package/esm/src/mcp-server/tools/queryAnswers.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/queryAnswers.js +44 -0
- package/esm/src/mcp-server/tools/queryAnswers.js.map +1 -0
- package/esm/src/mcp-server/tools/queryRetrieve.d.ts +7 -0
- package/esm/src/mcp-server/tools/queryRetrieve.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/queryRetrieve.js +46 -0
- package/esm/src/mcp-server/tools/queryRetrieve.js.map +1 -0
- package/esm/src/mcp-server/tools/querySearch.d.ts +7 -0
- package/esm/src/mcp-server/tools/querySearch.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/querySearch.js +35 -0
- package/esm/src/mcp-server/tools/querySearch.js.map +1 -0
- package/esm/src/mcp-server/tools/retrieveChunks.d.ts +7 -0
- package/esm/src/mcp-server/tools/retrieveChunks.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/retrieveChunks.js +46 -0
- package/esm/src/mcp-server/tools/retrieveChunks.js.map +1 -0
- package/esm/src/mcp-server/tools/searchAiSearch.d.ts +7 -0
- package/esm/src/mcp-server/tools/searchAiSearch.d.ts.map +1 -0
- package/esm/src/mcp-server/tools/searchAiSearch.js +35 -0
- package/esm/src/mcp-server/tools/searchAiSearch.js.map +1 -0
- package/esm/src/mcp-server/tools.d.ts +37 -0
- package/esm/src/mcp-server/tools.d.ts.map +1 -0
- package/esm/src/mcp-server/tools.js +78 -0
- package/esm/src/mcp-server/tools.js.map +1 -0
- package/esm/src/models/acceptlanguage.d.ts +7 -0
- package/esm/src/models/acceptlanguage.d.ts.map +1 -0
- package/esm/src/models/acceptlanguage.js +25 -0
- package/esm/src/models/acceptlanguage.js.map +1 -0
- package/esm/src/models/accessibleportal.d.ts +12 -0
- package/esm/src/models/accessibleportal.d.ts.map +1 -0
- package/esm/src/models/accessibleportal.js +14 -0
- package/esm/src/models/accessibleportal.js.map +1 -0
- package/esm/src/models/additionalsnippets.d.ts +17 -0
- package/esm/src/models/additionalsnippets.d.ts.map +1 -0
- package/esm/src/models/additionalsnippets.js +23 -0
- package/esm/src/models/additionalsnippets.js.map +1 -0
- package/esm/src/models/aisearchop.d.ts +23 -0
- package/esm/src/models/aisearchop.d.ts.map +1 -0
- package/esm/src/models/aisearchop.js +24 -0
- package/esm/src/models/aisearchop.js.map +1 -0
- package/esm/src/models/allaccessibleportals.d.ts +12 -0
- package/esm/src/models/allaccessibleportals.d.ts.map +1 -0
- package/esm/src/models/allaccessibleportals.js +11 -0
- package/esm/src/models/allaccessibleportals.js.map +1 -0
- package/esm/src/models/answersrequest.d.ts +28 -0
- package/esm/src/models/answersrequest.d.ts.map +1 -0
- package/esm/src/models/answersrequest.js +28 -0
- package/esm/src/models/answersrequest.js.map +1 -0
- package/esm/src/models/answersresponse.d.ts +34 -0
- package/esm/src/models/answersresponse.d.ts.map +1 -0
- package/esm/src/models/answersresponse.js +38 -0
- package/esm/src/models/answersresponse.js.map +1 -0
- package/esm/src/models/article.d.ts +59 -0
- package/esm/src/models/article.d.ts.map +1 -0
- package/esm/src/models/article.js +59 -0
- package/esm/src/models/article.js.map +1 -0
- package/esm/src/models/articleadditionalattributes.d.ts +4 -0
- package/esm/src/models/articleadditionalattributes.d.ts.map +1 -0
- package/esm/src/models/articleadditionalattributes.js +22 -0
- package/esm/src/models/articleadditionalattributes.js.map +1 -0
- package/esm/src/models/articleattachment.d.ts +16 -0
- package/esm/src/models/articleattachment.d.ts.map +1 -0
- package/esm/src/models/articleattachment.js +20 -0
- package/esm/src/models/articleattachment.js.map +1 -0
- package/esm/src/models/articleresult.d.ts +43 -0
- package/esm/src/models/articleresult.d.ts.map +1 -0
- package/esm/src/models/articleresult.js +42 -0
- package/esm/src/models/articleresult.js.map +1 -0
- package/esm/src/models/articleresultadditionalattributes.d.ts +4 -0
- package/esm/src/models/articleresultadditionalattributes.d.ts.map +1 -0
- package/esm/src/models/articleresultadditionalattributes.js +21 -0
- package/esm/src/models/articleresultadditionalattributes.js.map +1 -0
- package/esm/src/models/articleresults.d.ts +12 -0
- package/esm/src/models/articleresults.d.ts.map +1 -0
- package/esm/src/models/articleresults.js +11 -0
- package/esm/src/models/articleresults.js.map +1 -0
- package/esm/src/models/articlesearchresult.d.ts +57 -0
- package/esm/src/models/articlesearchresult.d.ts.map +1 -0
- package/esm/src/models/articlesearchresult.js +62 -0
- package/esm/src/models/articlesearchresult.js.map +1 -0
- package/esm/src/models/articletype.d.ts +17 -0
- package/esm/src/models/articletype.d.ts.map +1 -0
- package/esm/src/models/articletype.js +24 -0
- package/esm/src/models/articletype.js.map +1 -0
- package/esm/src/models/attachmentforcreatesuggestion.d.ts +9 -0
- package/esm/src/models/attachmentforcreatesuggestion.d.ts.map +1 -0
- package/esm/src/models/attachmentforcreatesuggestion.js +8 -0
- package/esm/src/models/attachmentforcreatesuggestion.js.map +1 -0
- package/esm/src/models/bookmarkstatus.d.ts +12 -0
- package/esm/src/models/bookmarkstatus.d.ts.map +1 -0
- package/esm/src/models/bookmarkstatus.js +11 -0
- package/esm/src/models/bookmarkstatus.js.map +1 -0
- package/esm/src/models/complianceforarticle.d.ts +24 -0
- package/esm/src/models/complianceforarticle.d.ts.map +1 -0
- package/esm/src/models/complianceforarticle.js +15 -0
- package/esm/src/models/complianceforarticle.js.map +1 -0
- package/esm/src/models/createdby.d.ts +10 -0
- package/esm/src/models/createdby.d.ts.map +1 -0
- package/esm/src/models/createdby.js +12 -0
- package/esm/src/models/createdby.js.map +1 -0
- package/esm/src/models/createsuggestion.d.ts +43 -0
- package/esm/src/models/createsuggestion.d.ts.map +1 -0
- package/esm/src/models/createsuggestion.js +47 -0
- package/esm/src/models/createsuggestion.js.map +1 -0
- package/esm/src/models/customattribute.d.ts +13 -0
- package/esm/src/models/customattribute.d.ts.map +1 -0
- package/esm/src/models/customattribute.js +19 -0
- package/esm/src/models/customattribute.js.map +1 -0
- package/esm/src/models/department.d.ts +7 -0
- package/esm/src/models/department.d.ts.map +1 -0
- package/esm/src/models/department.js +9 -0
- package/esm/src/models/department.js.map +1 -0
- package/esm/src/models/edition.d.ts +22 -0
- package/esm/src/models/edition.d.ts.map +1 -0
- package/esm/src/models/edition.js +18 -0
- package/esm/src/models/edition.js.map +1 -0
- package/esm/src/models/errors/apierror.d.ts +13 -0
- package/esm/src/models/errors/apierror.d.ts.map +1 -0
- package/esm/src/models/errors/apierror.js +12 -0
- package/esm/src/models/errors/apierror.js.map +1 -0
- package/esm/src/models/errors/httpclienterrors.d.ts +44 -0
- package/esm/src/models/errors/httpclienterrors.d.ts.map +1 -0
- package/esm/src/models/errors/httpclienterrors.js +56 -0
- package/esm/src/models/errors/httpclienterrors.js.map +1 -0
- package/esm/src/models/errors/sdkvalidationerror.d.ts +20 -0
- package/esm/src/models/errors/sdkvalidationerror.d.ts.map +1 -0
- package/esm/src/models/errors/sdkvalidationerror.js +85 -0
- package/esm/src/models/errors/sdkvalidationerror.js.map +1 -0
- package/esm/src/models/folderbreadcrumb.d.ts +10 -0
- package/esm/src/models/folderbreadcrumb.d.ts.map +1 -0
- package/esm/src/models/folderbreadcrumb.js +9 -0
- package/esm/src/models/folderbreadcrumb.js.map +1 -0
- package/esm/src/models/foldersummary.d.ts +12 -0
- package/esm/src/models/foldersummary.d.ts.map +1 -0
- package/esm/src/models/foldersummary.js +11 -0
- package/esm/src/models/foldersummary.js.map +1 -0
- package/esm/src/models/getannouncementarticlesop.d.ts +25 -0
- package/esm/src/models/getannouncementarticlesop.d.ts.map +1 -0
- package/esm/src/models/getannouncementarticlesop.js +26 -0
- package/esm/src/models/getannouncementarticlesop.js.map +1 -0
- package/esm/src/models/getarticlebyidop.d.ts +57 -0
- package/esm/src/models/getarticlebyidop.d.ts.map +1 -0
- package/esm/src/models/getarticlebyidop.js +130 -0
- package/esm/src/models/getarticlebyidop.js.map +1 -0
- package/esm/src/models/getmyportalsop.d.ts +28 -0
- package/esm/src/models/getmyportalsop.d.ts.map +1 -0
- package/esm/src/models/getmyportalsop.js +29 -0
- package/esm/src/models/getmyportalsop.js.map +1 -0
- package/esm/src/models/getpopulararticlesop.d.ts +24 -0
- package/esm/src/models/getpopulararticlesop.d.ts.map +1 -0
- package/esm/src/models/getpopulararticlesop.js +25 -0
- package/esm/src/models/getpopulararticlesop.js.map +1 -0
- package/esm/src/models/l10nstring.d.ts +7 -0
- package/esm/src/models/l10nstring.d.ts.map +1 -0
- package/esm/src/models/l10nstring.js +9 -0
- package/esm/src/models/l10nstring.js.map +1 -0
- package/esm/src/models/languagecode.d.ts +7 -0
- package/esm/src/models/languagecode.d.ts.map +1 -0
- package/esm/src/models/languagecode.js +40 -0
- package/esm/src/models/languagecode.js.map +1 -0
- package/esm/src/models/languagecodeparameter.d.ts +7 -0
- package/esm/src/models/languagecodeparameter.d.ts.map +1 -0
- package/esm/src/models/languagecodeparameter.js +41 -0
- package/esm/src/models/languagecodeparameter.js.map +1 -0
- package/esm/src/models/languagequeryparameter.d.ts +7 -0
- package/esm/src/models/languagequeryparameter.d.ts.map +1 -0
- package/esm/src/models/languagequeryparameter.js +41 -0
- package/esm/src/models/languagequeryparameter.js.map +1 -0
- package/esm/src/models/link.d.ts +10 -0
- package/esm/src/models/link.d.ts.map +1 -0
- package/esm/src/models/link.js +9 -0
- package/esm/src/models/link.js.map +1 -0
- package/esm/src/models/makesuggestionop.d.ts +18 -0
- package/esm/src/models/makesuggestionop.d.ts.map +1 -0
- package/esm/src/models/makesuggestionop.js +19 -0
- package/esm/src/models/makesuggestionop.js.map +1 -0
- package/esm/src/models/mandatorylanguagequeryparameter.d.ts +7 -0
- package/esm/src/models/mandatorylanguagequeryparameter.d.ts.map +1 -0
- package/esm/src/models/mandatorylanguagequeryparameter.js +40 -0
- package/esm/src/models/mandatorylanguagequeryparameter.js.map +1 -0
- package/esm/src/models/milestone.d.ts +10 -0
- package/esm/src/models/milestone.d.ts.map +1 -0
- package/esm/src/models/milestone.js +9 -0
- package/esm/src/models/milestone.js.map +1 -0
- package/esm/src/models/milestonename.d.ts +10 -0
- package/esm/src/models/milestonename.d.ts.map +1 -0
- package/esm/src/models/milestonename.js +9 -0
- package/esm/src/models/milestonename.js.map +1 -0
- package/esm/src/models/modifiedby.d.ts +10 -0
- package/esm/src/models/modifiedby.d.ts.map +1 -0
- package/esm/src/models/modifiedby.js +12 -0
- package/esm/src/models/modifiedby.js.map +1 -0
- package/esm/src/models/order.d.ts +4 -0
- package/esm/src/models/order.d.ts.map +1 -0
- package/esm/src/models/order.js +9 -0
- package/esm/src/models/order.js.map +1 -0
- package/esm/src/models/ownedby.d.ts +10 -0
- package/esm/src/models/ownedby.d.ts.map +1 -0
- package/esm/src/models/ownedby.js +13 -0
- package/esm/src/models/ownedby.js.map +1 -0
- package/esm/src/models/paginationinfo.d.ts +10 -0
- package/esm/src/models/paginationinfo.d.ts.map +1 -0
- package/esm/src/models/paginationinfo.js +12 -0
- package/esm/src/models/paginationinfo.js.map +1 -0
- package/esm/src/models/personalization.d.ts +25 -0
- package/esm/src/models/personalization.d.ts.map +1 -0
- package/esm/src/models/personalization.js +22 -0
- package/esm/src/models/personalization.js.map +1 -0
- package/esm/src/models/postportalidanswersop.d.ts +24 -0
- package/esm/src/models/postportalidanswersop.d.ts.map +1 -0
- package/esm/src/models/postportalidanswersop.js +25 -0
- package/esm/src/models/postportalidanswersop.js.map +1 -0
- package/esm/src/models/postportalidretrieveop.d.ts +26 -0
- package/esm/src/models/postportalidretrieveop.d.ts.map +1 -0
- package/esm/src/models/postportalidretrieveop.js +27 -0
- package/esm/src/models/postportalidretrieveop.js.map +1 -0
- package/esm/src/models/publishview.d.ts +13 -0
- package/esm/src/models/publishview.d.ts.map +1 -0
- package/esm/src/models/publishview.js +15 -0
- package/esm/src/models/publishview.js.map +1 -0
- package/esm/src/models/referenceresponse.d.ts +25 -0
- package/esm/src/models/referenceresponse.d.ts.map +1 -0
- package/esm/src/models/referenceresponse.js +30 -0
- package/esm/src/models/referenceresponse.js.map +1 -0
- package/esm/src/models/retrieverequest.d.ts +18 -0
- package/esm/src/models/retrieverequest.d.ts.map +1 -0
- package/esm/src/models/retrieverequest.js +21 -0
- package/esm/src/models/retrieverequest.js.map +1 -0
- package/esm/src/models/retrieveresponse.d.ts +37 -0
- package/esm/src/models/retrieveresponse.d.ts.map +1 -0
- package/esm/src/models/retrieveresponse.js +37 -0
- package/esm/src/models/retrieveresponse.js.map +1 -0
- package/esm/src/models/schemaslink.d.ts +8 -0
- package/esm/src/models/schemaslink.d.ts.map +1 -0
- package/esm/src/models/schemaslink.js +10 -0
- package/esm/src/models/schemaslink.js.map +1 -0
- package/esm/src/models/searchresponse.d.ts +14 -0
- package/esm/src/models/searchresponse.d.ts.map +1 -0
- package/esm/src/models/searchresponse.js +12 -0
- package/esm/src/models/searchresponse.js.map +1 -0
- package/esm/src/models/searchresult.d.ts +27 -0
- package/esm/src/models/searchresult.d.ts.map +1 -0
- package/esm/src/models/searchresult.js +32 -0
- package/esm/src/models/searchresult.js.map +1 -0
- package/esm/src/models/security.d.ts +6 -0
- package/esm/src/models/security.d.ts.map +1 -0
- package/esm/src/models/security.js +9 -0
- package/esm/src/models/security.js.map +1 -0
- package/esm/src/models/shorturl.d.ts +9 -0
- package/esm/src/models/shorturl.d.ts.map +1 -0
- package/esm/src/models/shorturl.js +12 -0
- package/esm/src/models/shorturl.js.map +1 -0
- package/esm/src/models/sortidnamedepartment.d.ts +4 -0
- package/esm/src/models/sortidnamedepartment.d.ts.map +1 -0
- package/esm/src/models/sortidnamedepartment.js +10 -0
- package/esm/src/models/sortidnamedepartment.js.map +1 -0
- package/esm/src/models/stage.d.ts +11 -0
- package/esm/src/models/stage.d.ts.map +1 -0
- package/esm/src/models/stage.js +11 -0
- package/esm/src/models/stage.js.map +1 -0
- package/esm/src/models/structuredauthoringfields.d.ts +10 -0
- package/esm/src/models/structuredauthoringfields.d.ts.map +1 -0
- package/esm/src/models/structuredauthoringfields.js +12 -0
- package/esm/src/models/structuredauthoringfields.js.map +1 -0
- package/esm/src/models/tag.d.ts +7 -0
- package/esm/src/models/tag.d.ts.map +1 -0
- package/esm/src/models/tag.js +9 -0
- package/esm/src/models/tag.js.map +1 -0
- package/esm/src/models/tagcategory.d.ts +14 -0
- package/esm/src/models/tagcategory.d.ts.map +1 -0
- package/esm/src/models/tagcategory.js +13 -0
- package/esm/src/models/tagcategory.js.map +1 -0
- package/esm/src/models/taggroup.d.ts +7 -0
- package/esm/src/models/taggroup.d.ts.map +1 -0
- package/esm/src/models/taggroup.js +10 -0
- package/esm/src/models/taggroup.js.map +1 -0
- package/esm/src/models/taggroups.d.ts +7 -0
- package/esm/src/models/taggroups.d.ts.map +1 -0
- package/esm/src/models/taggroups.js +9 -0
- package/esm/src/models/taggroups.js.map +1 -0
- package/esm/src/models/tags.d.ts +7 -0
- package/esm/src/models/tags.d.ts.map +1 -0
- package/esm/src/models/tags.js +9 -0
- package/esm/src/models/tags.js.map +1 -0
- package/esm/src/models/topicbreadcrumb.d.ts +10 -0
- package/esm/src/models/topicbreadcrumb.d.ts.map +1 -0
- package/esm/src/models/topicbreadcrumb.js +9 -0
- package/esm/src/models/topicbreadcrumb.js.map +1 -0
- package/esm/src/models/topicsummary.d.ts +10 -0
- package/esm/src/models/topicsummary.d.ts.map +1 -0
- package/esm/src/models/topicsummary.js +9 -0
- package/esm/src/models/topicsummary.js.map +1 -0
- package/esm/src/models/workflow.d.ts +10 -0
- package/esm/src/models/workflow.d.ts.map +1 -0
- package/esm/src/models/workflow.js +10 -0
- package/esm/src/models/workflow.js.map +1 -0
- package/esm/src/models/workflowmilestone.d.ts +4 -0
- package/esm/src/models/workflowmilestone.d.ts.map +1 -0
- package/esm/src/models/workflowmilestone.js +10 -0
- package/esm/src/models/workflowmilestone.js.map +1 -0
- package/esm/src/models/wserrorcommon.d.ts +17 -0
- package/esm/src/models/wserrorcommon.d.ts.map +1 -0
- package/esm/src/models/wserrorcommon.js +16 -0
- package/esm/src/models/wserrorcommon.js.map +1 -0
- package/esm/src/types/async.d.ts +23 -0
- package/esm/src/types/async.d.ts.map +1 -0
- package/esm/src/types/async.js +28 -0
- package/esm/src/types/async.js.map +1 -0
- package/esm/src/types/blobs.d.ts +2 -0
- package/esm/src/types/blobs.d.ts.map +1 -0
- package/esm/src/types/blobs.js +20 -0
- package/esm/src/types/blobs.js.map +1 -0
- package/esm/src/types/enums.d.ts +12 -0
- package/esm/src/types/enums.d.ts.map +1 -0
- package/esm/src/types/enums.js +7 -0
- package/esm/src/types/enums.js.map +1 -0
- package/esm/src/types/fp.d.ts +31 -0
- package/esm/src/types/fp.d.ts.map +1 -0
- package/esm/src/types/fp.js +31 -0
- package/esm/src/types/fp.js.map +1 -0
- package/esm/src/types/rfcdate.d.ts +21 -0
- package/esm/src/types/rfcdate.d.ts.map +1 -0
- package/esm/src/types/rfcdate.js +43 -0
- package/esm/src/types/rfcdate.js.map +1 -0
- package/esm/src/types/streams.d.ts +2 -0
- package/esm/src/types/streams.d.ts.map +1 -0
- package/esm/src/types/streams.js +15 -0
- package/esm/src/types/streams.js.map +1 -0
- package/esm/worker-configuration.d.ts +4 -0
- package/esm/worker-configuration.d.ts.map +1 -0
- package/esm/worker-configuration.js +5 -0
- package/esm/worker-configuration.js.map +1 -0
- package/manifest.json +96 -0
- package/package.json +41 -0
- package/scripts/login.js +93 -0
- package/scripts/logout.js +53 -0
- package/src/cloudflare-worker/cloudflare-worker.ts +98 -0
- package/src/cloudflare-worker/landing-page.ts +337 -0
- package/src/core.ts +13 -0
- package/src/funcs/getAnnouncements.ts +202 -0
- package/src/funcs/getArticle.ts +208 -0
- package/src/funcs/getPopularArticles.ts +198 -0
- package/src/funcs/getPortals.ts +190 -0
- package/src/funcs/makeSuggestion.ts +191 -0
- package/src/funcs/queryAnswers.ts +191 -0
- package/src/funcs/queryRetrieve.ts +197 -0
- package/src/funcs/querySearch.ts +181 -0
- package/src/hooks/auth-hook.ts +1752 -0
- package/src/hooks/auth-pages/config-page.js +531 -0
- package/src/hooks/hooks.ts +132 -0
- package/src/hooks/portal-cache-hook.ts +505 -0
- package/src/hooks/portal-lookup-hook.ts +429 -0
- package/src/hooks/registration.ts +43 -0
- package/src/hooks/server-routing-hook.ts +58 -0
- package/src/hooks/types.ts +107 -0
- package/src/lib/base64.ts +37 -0
- package/src/lib/config.ts +89 -0
- package/src/lib/dlv.ts +53 -0
- package/src/lib/encodings.ts +483 -0
- package/src/lib/env.ts +41 -0
- package/src/lib/files.ts +82 -0
- package/src/lib/http.ts +323 -0
- package/src/lib/is-plain-object.ts +43 -0
- package/src/lib/logger.ts +9 -0
- package/src/lib/matchers.ts +350 -0
- package/src/lib/primitives.ts +113 -0
- package/src/lib/result.ts +50 -0
- package/src/lib/retries.ts +218 -0
- package/src/lib/schemas.ts +91 -0
- package/src/lib/sdks.ts +409 -0
- package/src/lib/security.ts +260 -0
- package/src/lib/url.ts +33 -0
- package/src/mcp-server/cli/start/command.ts +100 -0
- package/src/mcp-server/cli/start/impl.ts +133 -0
- package/src/mcp-server/cli.ts +13 -0
- package/src/mcp-server/console-logger.ts +76 -0
- package/src/mcp-server/extensions.ts +17 -0
- package/src/mcp-server/mcp-server.ts +26 -0
- package/src/mcp-server/prompts.ts +121 -0
- package/src/mcp-server/resources.ts +176 -0
- package/src/mcp-server/scopes.ts +7 -0
- package/src/mcp-server/server.ts +91 -0
- package/src/mcp-server/shared.ts +78 -0
- package/src/mcp-server/tools/getAnnouncements.ts +53 -0
- package/src/mcp-server/tools/getArticle.ts +56 -0
- package/src/mcp-server/tools/getPopularArticles.ts +53 -0
- package/src/mcp-server/tools/getPortals.ts +51 -0
- package/src/mcp-server/tools/makeSuggestion.ts +53 -0
- package/src/mcp-server/tools/queryAnswers.ts +53 -0
- package/src/mcp-server/tools/queryRetrieve.ts +55 -0
- package/src/mcp-server/tools/querySearch.ts +44 -0
- package/src/mcp-server/tools.ts +156 -0
- package/src/models/acceptlanguage.ts +30 -0
- package/src/models/accessibleportal.ts +27 -0
- package/src/models/additionalsnippets.ts +43 -0
- package/src/models/aisearchop.ts +62 -0
- package/src/models/allaccessibleportals.ts +27 -0
- package/src/models/answersrequest.ts +67 -0
- package/src/models/answersresponse.ts +88 -0
- package/src/models/article.ts +120 -0
- package/src/models/articleadditionalattributes.ts +27 -0
- package/src/models/articleattachment.ts +38 -0
- package/src/models/articleresult.ts +91 -0
- package/src/models/articleresults.ts +24 -0
- package/src/models/articlesearchresult.ts +139 -0
- package/src/models/articletype.ts +42 -0
- package/src/models/attachmentforcreatesuggestion.ts +18 -0
- package/src/models/bookmarkstatus.ts +28 -0
- package/src/models/complianceforarticle.ts +48 -0
- package/src/models/createdby.ts +22 -0
- package/src/models/createsuggestion.ts +108 -0
- package/src/models/customattribute.ts +33 -0
- package/src/models/department.ts +16 -0
- package/src/models/edition.ts +40 -0
- package/src/models/errors/apierror.ts +21 -0
- package/src/models/errors/httpclienterrors.ts +62 -0
- package/src/models/errors/sdkvalidationerror.ts +97 -0
- package/src/models/folderbreadcrumb.ts +21 -0
- package/src/models/foldersummary.ts +27 -0
- package/src/models/getannouncementarticlesop.ts +68 -0
- package/src/models/getarticlebyidop.ts +182 -0
- package/src/models/getmyportalsop.ts +78 -0
- package/src/models/getpopulararticlesop.ts +66 -0
- package/src/models/l10nstring.ts +19 -0
- package/src/models/languagecode.ts +43 -0
- package/src/models/languagecodeparameter.ts +48 -0
- package/src/models/languagequeryparameter.ts +48 -0
- package/src/models/link.ts +17 -0
- package/src/models/makesuggestionop.ts +47 -0
- package/src/models/mandatorylanguagequeryparameter.ts +47 -0
- package/src/models/milestone.ts +16 -0
- package/src/models/milestonename.ts +22 -0
- package/src/models/modifiedby.ts +25 -0
- package/src/models/order.ts +12 -0
- package/src/models/ownedby.ts +22 -0
- package/src/models/paginationinfo.ts +24 -0
- package/src/models/personalization.ts +55 -0
- package/src/models/postportalidanswersop.ts +62 -0
- package/src/models/postportalidretrieveop.ts +68 -0
- package/src/models/publishview.ts +35 -0
- package/src/models/referenceresponse.ts +60 -0
- package/src/models/retrieverequest.ts +47 -0
- package/src/models/retrieveresponse.ts +92 -0
- package/src/models/schemaslink.ts +21 -0
- package/src/models/searchresponse.ts +34 -0
- package/src/models/searchresult.ts +64 -0
- package/src/models/security.ts +14 -0
- package/src/models/shorturl.ts +20 -0
- package/src/models/sortidnamedepartment.ts +15 -0
- package/src/models/stage.ts +20 -0
- package/src/models/structuredauthoringfields.ts +24 -0
- package/src/models/tagcategory.ts +28 -0
- package/src/models/taggroup.ts +13 -0
- package/src/models/taggroups.ts +13 -0
- package/src/models/tags.ts +15 -0
- package/src/models/topicbreadcrumb.ts +21 -0
- package/src/models/topicsummary.ts +24 -0
- package/src/models/workflow.ts +16 -0
- package/src/models/workflowmilestone.ts +13 -0
- package/src/models/wserrorcommon.ts +34 -0
- package/src/types/async.ts +68 -0
- package/src/types/blobs.ts +23 -0
- package/src/types/enums.ts +16 -0
- package/src/types/fp.ts +50 -0
- package/src/types/rfcdate.ts +54 -0
- package/src/types/streams.ts +21 -0
- package/tsconfig.json +36 -0
- package/worker-configuration.d.ts +8489 -0
- package/worker-configuration.ts +11 -0
|
@@ -0,0 +1,1565 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Hook for eGain MCP Server
|
|
3
|
+
* Handles Azure B2C OAuth2 authentication with PKCE and popup window
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import { exec } from "child_process";
|
|
8
|
+
import * as http from "http";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import * as crypto from "crypto";
|
|
12
|
+
import { promisify } from "util";
|
|
13
|
+
// ES module __dirname equivalent
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
// Get the project root directory by finding package.json
|
|
17
|
+
const getProjectRoot = () => {
|
|
18
|
+
// Start from current directory and walk up until we find package.json
|
|
19
|
+
let currentDir = __dirname;
|
|
20
|
+
while (currentDir !== path.dirname(currentDir)) { // Stop at filesystem root
|
|
21
|
+
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
|
|
22
|
+
return currentDir;
|
|
23
|
+
}
|
|
24
|
+
currentDir = path.dirname(currentDir);
|
|
25
|
+
}
|
|
26
|
+
// If package.json not found, use current working directory as fallback
|
|
27
|
+
return process.cwd();
|
|
28
|
+
};
|
|
29
|
+
// Get user's home directory for storing config securely
|
|
30
|
+
const getConfigDir = () => {
|
|
31
|
+
const homeDir = os.homedir();
|
|
32
|
+
const configDir = path.join(homeDir, '.egain-mcp');
|
|
33
|
+
// Create directory if it doesn't exist
|
|
34
|
+
if (!fs.existsSync(configDir)) {
|
|
35
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 }); // Only user can read/write
|
|
36
|
+
}
|
|
37
|
+
return configDir;
|
|
38
|
+
};
|
|
39
|
+
const getConfigPath = () => {
|
|
40
|
+
return path.join(getConfigDir(), 'config.json');
|
|
41
|
+
};
|
|
42
|
+
const execAsync = promisify(exec);
|
|
43
|
+
const CONFIG_SERVER_PORT = 3333;
|
|
44
|
+
const CONFIG_SERVER_HOST = 'localhost';
|
|
45
|
+
export class AuthenticationHook {
|
|
46
|
+
token = null;
|
|
47
|
+
authConfig;
|
|
48
|
+
codeVerifier;
|
|
49
|
+
codeChallenge;
|
|
50
|
+
portalCacheHook; // PortalCacheHook reference
|
|
51
|
+
configServer = null; // HTTP server for configuration form
|
|
52
|
+
detectedBrowser = 'Google Chrome'; // Default fallback
|
|
53
|
+
authCancelled = false; // Track if user cancelled authentication
|
|
54
|
+
oauthRedirectStarted = false; // Track when OAuth redirect happens
|
|
55
|
+
/**
|
|
56
|
+
* Generates PKCE (Proof Key for Code Exchange) values for OAuth2 security
|
|
57
|
+
* @returns Object containing code verifier and S256 code challenge
|
|
58
|
+
*/
|
|
59
|
+
generatePKCEValues() {
|
|
60
|
+
// Generate cryptographically secure random code verifier (43-128 characters)
|
|
61
|
+
// Using base64url encoding of 32 random bytes = 43 characters
|
|
62
|
+
const codeVerifier = crypto
|
|
63
|
+
.randomBytes(32)
|
|
64
|
+
.toString('base64url'); // base64url automatically removes padding and uses URL-safe characters
|
|
65
|
+
// Generate S256 code challenge from verifier
|
|
66
|
+
const codeChallenge = crypto
|
|
67
|
+
.createHash('sha256')
|
|
68
|
+
.update(codeVerifier)
|
|
69
|
+
.digest('base64url'); // base64url encoding for URL safety
|
|
70
|
+
console.error('🔐 Generated new PKCE values:');
|
|
71
|
+
console.error(` Code Verifier: ${codeVerifier.substring(0, 10)}... (${codeVerifier.length} chars)`);
|
|
72
|
+
console.error(` Code Challenge: ${codeChallenge.substring(0, 10)}... (${codeChallenge.length} chars)`);
|
|
73
|
+
return {
|
|
74
|
+
codeVerifier,
|
|
75
|
+
codeChallenge
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
constructor(portalCacheHook) {
|
|
79
|
+
this.portalCacheHook = portalCacheHook;
|
|
80
|
+
this.authConfig = this.loadAuthConfig();
|
|
81
|
+
// Generate secure PKCE values instead of using hardcoded ones
|
|
82
|
+
const pkceValues = this.generatePKCEValues();
|
|
83
|
+
this.codeVerifier = pkceValues.codeVerifier;
|
|
84
|
+
this.codeChallenge = pkceValues.codeChallenge;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Detect the default browser on macOS or Windows
|
|
88
|
+
*/
|
|
89
|
+
async detectDefaultBrowser() {
|
|
90
|
+
console.error('🔍 Attempting to detect default browser...');
|
|
91
|
+
const platform = process.platform;
|
|
92
|
+
// Force specific browser for testing if FORCE_BROWSER env var is set
|
|
93
|
+
const forcedBrowser = process.env['FORCE_BROWSER'] || (process.env['FORCE_SAFARI_BROWSER'] === 'true' ? 'Safari' : null);
|
|
94
|
+
if (forcedBrowser) {
|
|
95
|
+
console.error(` 🧪 TEST MODE: Forcing ${forcedBrowser} browser...`);
|
|
96
|
+
this.detectedBrowser = forcedBrowser;
|
|
97
|
+
if (forcedBrowser === 'Safari') {
|
|
98
|
+
console.error(`⚠️ Found Safari, but it has limited private browsing support via CLI`);
|
|
99
|
+
console.error(` For better security, consider installing Chrome, Firefox, or Edge`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.error(`✅ Using ${forcedBrowser} for testing`);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (platform === 'darwin') {
|
|
107
|
+
await this.detectBrowserMacOS();
|
|
108
|
+
}
|
|
109
|
+
else if (platform === 'win32') {
|
|
110
|
+
await this.detectBrowserWindows();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Detect browser on macOS
|
|
115
|
+
*/
|
|
116
|
+
async detectBrowserMacOS() {
|
|
117
|
+
// Map bundle IDs to application names
|
|
118
|
+
const browserMap = {
|
|
119
|
+
'com.google.chrome': 'Google Chrome',
|
|
120
|
+
'com.apple.safari': 'Safari',
|
|
121
|
+
'org.mozilla.firefox': 'Firefox',
|
|
122
|
+
'com.microsoft.edgemac': 'Microsoft Edge',
|
|
123
|
+
'com.brave.browser': 'Brave Browser',
|
|
124
|
+
'com.vivaldi.vivaldi': 'Vivaldi',
|
|
125
|
+
'com.operasoftware.opera': 'Opera'
|
|
126
|
+
};
|
|
127
|
+
// Method 1: Try using defaultbrowser command (if installed)
|
|
128
|
+
try {
|
|
129
|
+
console.error(' Method 1: Trying defaultbrowser command...');
|
|
130
|
+
const { stdout } = await execAsync('which defaultbrowser');
|
|
131
|
+
if (stdout.trim()) {
|
|
132
|
+
const { stdout: browserOutput } = await execAsync('defaultbrowser');
|
|
133
|
+
const bundleId = browserOutput.trim();
|
|
134
|
+
console.error(` Bundle ID from defaultbrowser: ${bundleId}`);
|
|
135
|
+
if (bundleId && browserMap[bundleId]) {
|
|
136
|
+
this.detectedBrowser = browserMap[bundleId];
|
|
137
|
+
console.error(`✅ Successfully detected: ${this.detectedBrowser}`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error(' Method 1 failed (defaultbrowser not installed)');
|
|
144
|
+
}
|
|
145
|
+
// Method 2: Check LaunchServices
|
|
146
|
+
try {
|
|
147
|
+
console.error(' Method 2: Checking LaunchServices...');
|
|
148
|
+
const { stdout } = await execAsync(`defaults read com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers 2>/dev/null | grep -B 1 -A 3 'https' | grep LSHandlerRoleAll -A 2 | grep LSHandlerContentTag | head -1 | cut -d '"' -f 2`);
|
|
149
|
+
const bundleId = stdout.trim();
|
|
150
|
+
console.error(` Bundle ID from LaunchServices: ${bundleId || '(none)'}`);
|
|
151
|
+
if (bundleId && browserMap[bundleId]) {
|
|
152
|
+
this.detectedBrowser = browserMap[bundleId];
|
|
153
|
+
console.error(`✅ Successfully detected: ${this.detectedBrowser}`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.error(' Method 2 failed');
|
|
159
|
+
}
|
|
160
|
+
// Method 3: Scan for installed browsers
|
|
161
|
+
console.error(' Method 3: Scanning for installed browsers...');
|
|
162
|
+
const browsersToCheck = [
|
|
163
|
+
'Google Chrome',
|
|
164
|
+
'Microsoft Edge',
|
|
165
|
+
'Firefox',
|
|
166
|
+
'Brave Browser',
|
|
167
|
+
'Vivaldi',
|
|
168
|
+
'Opera',
|
|
169
|
+
'Safari'
|
|
170
|
+
];
|
|
171
|
+
for (const browser of browsersToCheck) {
|
|
172
|
+
try {
|
|
173
|
+
await execAsync(`osascript -e 'exists application "${browser}"'`);
|
|
174
|
+
this.detectedBrowser = browser;
|
|
175
|
+
if (browser === 'Safari') {
|
|
176
|
+
console.error(`⚠️ Found Safari, but it has limited private browsing support via CLI`);
|
|
177
|
+
console.error(` For better security, consider installing Chrome, Firefox, or Edge`);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
console.error(`✅ Found installed browser: ${this.detectedBrowser}`);
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
// Browser not installed, continue
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
console.error(`⚠️ No browsers detected, falling back to: ${this.detectedBrowser}`);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Detect browser on Windows
|
|
192
|
+
*/
|
|
193
|
+
async detectBrowserWindows() {
|
|
194
|
+
console.error(' Method 1: Checking Windows registry for default browser...');
|
|
195
|
+
// Try to get default browser from Windows registry
|
|
196
|
+
try {
|
|
197
|
+
const { stdout } = await execAsync(`powershell -Command "$browser = (Get-ItemProperty 'HKCU:\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice' -ErrorAction SilentlyContinue).ProgId; Write-Output $browser"`);
|
|
198
|
+
const progId = stdout.trim().toLowerCase();
|
|
199
|
+
console.error(` ProgID detected: ${progId || '(none)'}`);
|
|
200
|
+
// Map Windows ProgIDs to browser names
|
|
201
|
+
if (progId.includes('chromehtml')) {
|
|
202
|
+
this.detectedBrowser = 'chrome';
|
|
203
|
+
console.error(`✅ Successfully detected: Google Chrome`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
else if (progId.includes('msedgehtm')) {
|
|
207
|
+
this.detectedBrowser = 'msedge';
|
|
208
|
+
console.error(`✅ Successfully detected: Microsoft Edge`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
else if (progId.includes('firefox')) {
|
|
212
|
+
this.detectedBrowser = 'firefox';
|
|
213
|
+
console.error(`✅ Successfully detected: Firefox`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
else if (progId.includes('bravehtml')) {
|
|
217
|
+
this.detectedBrowser = 'brave';
|
|
218
|
+
console.error(`✅ Successfully detected: Brave`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
console.error(' Method 1 failed');
|
|
224
|
+
}
|
|
225
|
+
// Method 2: Scan for installed browsers in common locations
|
|
226
|
+
console.error(' Method 2: Scanning for installed browsers...');
|
|
227
|
+
const browsersToCheck = [
|
|
228
|
+
{ name: 'chrome', paths: [
|
|
229
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
230
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
|
|
231
|
+
] },
|
|
232
|
+
{ name: 'msedge', paths: [
|
|
233
|
+
'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
234
|
+
'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe'
|
|
235
|
+
] },
|
|
236
|
+
{ name: 'firefox', paths: [
|
|
237
|
+
'C:\\Program Files\\Mozilla Firefox\\firefox.exe',
|
|
238
|
+
'C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe'
|
|
239
|
+
] },
|
|
240
|
+
{ name: 'brave', paths: [
|
|
241
|
+
'C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe',
|
|
242
|
+
'C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe'
|
|
243
|
+
] }
|
|
244
|
+
];
|
|
245
|
+
for (const browser of browsersToCheck) {
|
|
246
|
+
for (const browserPath of browser.paths) {
|
|
247
|
+
try {
|
|
248
|
+
// Check if file exists using PowerShell
|
|
249
|
+
const { stdout } = await execAsync(`powershell -Command "Test-Path '${browserPath}'"`);
|
|
250
|
+
if (stdout.trim().toLowerCase() === 'true') {
|
|
251
|
+
this.detectedBrowser = browser.name;
|
|
252
|
+
const displayName = browser.name === 'msedge' ? 'Microsoft Edge' :
|
|
253
|
+
browser.name === 'chrome' ? 'Google Chrome' :
|
|
254
|
+
browser.name === 'firefox' ? 'Firefox' : 'Brave';
|
|
255
|
+
console.error(`✅ Found installed browser: ${displayName}`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
// Continue checking other paths
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Default to Edge (ships with Windows 10+)
|
|
265
|
+
console.error(`⚠️ No browsers detected, falling back to: Microsoft Edge`);
|
|
266
|
+
this.detectedBrowser = 'msedge';
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Get incognito flag for the detected browser
|
|
270
|
+
*/
|
|
271
|
+
getIncognitoFlag() {
|
|
272
|
+
const flagMap = {
|
|
273
|
+
// macOS browser names
|
|
274
|
+
'Google Chrome': '--incognito',
|
|
275
|
+
'Brave Browser': '--incognito',
|
|
276
|
+
'Microsoft Edge': '--inprivate',
|
|
277
|
+
'Vivaldi': '--incognito',
|
|
278
|
+
'Opera': '--private',
|
|
279
|
+
'Firefox': '--private-window',
|
|
280
|
+
'Safari': '', // Safari doesn't support private browsing via CLI well
|
|
281
|
+
// Windows browser executable names
|
|
282
|
+
'chrome': '--incognito',
|
|
283
|
+
'msedge': '--inprivate',
|
|
284
|
+
'firefox': '--private-window',
|
|
285
|
+
'brave': '--incognito'
|
|
286
|
+
};
|
|
287
|
+
// Use 'in' operator to check if key exists, allowing empty string values
|
|
288
|
+
if (this.detectedBrowser in flagMap) {
|
|
289
|
+
return flagMap[this.detectedBrowser]; // Non-null assertion since we just checked
|
|
290
|
+
}
|
|
291
|
+
return '--incognito';
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Save OAuth config to user's home directory (secure file storage)
|
|
295
|
+
*/
|
|
296
|
+
saveConfigToFile(config) {
|
|
297
|
+
try {
|
|
298
|
+
const configPath = getConfigPath();
|
|
299
|
+
// Set restrictive permissions (only current user can read/write)
|
|
300
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), {
|
|
301
|
+
mode: 0o600
|
|
302
|
+
});
|
|
303
|
+
console.error(`💾 Created config file: ${configPath}`);
|
|
304
|
+
console.error(' (Secured with user-only read/write permissions)');
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
console.error('❌ Failed to save config:', error);
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Load OAuth config from user's home directory, falling back to .env
|
|
313
|
+
*/
|
|
314
|
+
loadAuthConfig() {
|
|
315
|
+
// Priority 1: Try loading from secure user config file
|
|
316
|
+
try {
|
|
317
|
+
const configPath = getConfigPath();
|
|
318
|
+
if (fs.existsSync(configPath)) {
|
|
319
|
+
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
320
|
+
const config = JSON.parse(configContent);
|
|
321
|
+
console.error(`✅ Loaded config from: ${configPath}`);
|
|
322
|
+
return config;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
console.error('⚠️ Could not load config from home directory:', error);
|
|
327
|
+
}
|
|
328
|
+
// Priority 2: Try loading from .env file (for development)
|
|
329
|
+
try {
|
|
330
|
+
// Try multiple possible locations for the .env file
|
|
331
|
+
const possiblePaths = [
|
|
332
|
+
path.join(process.cwd(), '.env'),
|
|
333
|
+
path.join(__dirname, '../../../.env'), // From compiled esm location in bin/
|
|
334
|
+
path.join(__dirname, '../../../../.env'), // From compiled bin/ to project root
|
|
335
|
+
path.join(__dirname, '../../.env'), // From src location
|
|
336
|
+
// Try finding project root by looking for package.json
|
|
337
|
+
...this.findProjectRoot().map(root => path.join(root, '.env'))
|
|
338
|
+
];
|
|
339
|
+
let envPath = null;
|
|
340
|
+
for (const possiblePath of possiblePaths) {
|
|
341
|
+
if (fs.existsSync(possiblePath)) {
|
|
342
|
+
envPath = possiblePath;
|
|
343
|
+
console.error(`✅ Found .env file at: ${envPath}`);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (envPath) {
|
|
348
|
+
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
349
|
+
const config = {};
|
|
350
|
+
envContent.split('\n').forEach(line => {
|
|
351
|
+
const equalIndex = line.indexOf('=');
|
|
352
|
+
if (equalIndex > 0) {
|
|
353
|
+
const key = line.substring(0, equalIndex).trim();
|
|
354
|
+
const value = line.substring(equalIndex + 1).trim();
|
|
355
|
+
if (key && value) {
|
|
356
|
+
const cleanKey = key;
|
|
357
|
+
const cleanValue = value.replace(/['"]/g, '');
|
|
358
|
+
switch (cleanKey) {
|
|
359
|
+
// New variable names (preferred)
|
|
360
|
+
case 'EGAIN_URL':
|
|
361
|
+
config.environmentUrl = cleanValue;
|
|
362
|
+
break;
|
|
363
|
+
case 'CLIENT_ID':
|
|
364
|
+
config.clientId = cleanValue;
|
|
365
|
+
break;
|
|
366
|
+
case 'CLIENT_SECRET':
|
|
367
|
+
config.clientSecret = cleanValue;
|
|
368
|
+
break;
|
|
369
|
+
case 'REDIRECT_URL':
|
|
370
|
+
config.redirectUri = cleanValue;
|
|
371
|
+
break;
|
|
372
|
+
case 'AUTH_URL':
|
|
373
|
+
config.authUrl = cleanValue;
|
|
374
|
+
break;
|
|
375
|
+
case 'ACCESS_TOKEN_URL':
|
|
376
|
+
config.accessUrl = cleanValue;
|
|
377
|
+
break;
|
|
378
|
+
case 'SCOPE_PREFIX':
|
|
379
|
+
config.scopePrefix = cleanValue;
|
|
380
|
+
break;
|
|
381
|
+
// Backward compatibility with old names
|
|
382
|
+
case 'EGAIN_ENVIRONMENT_URL':
|
|
383
|
+
config.environmentUrl = cleanValue;
|
|
384
|
+
break;
|
|
385
|
+
case 'EGAIN_CLIENT_ID':
|
|
386
|
+
config.clientId = cleanValue;
|
|
387
|
+
break;
|
|
388
|
+
case 'EGAIN_CLIENT_SECRET':
|
|
389
|
+
config.clientSecret = cleanValue;
|
|
390
|
+
break;
|
|
391
|
+
case 'EGAIN_REDIRECT_URI':
|
|
392
|
+
config.redirectUri = cleanValue;
|
|
393
|
+
break;
|
|
394
|
+
case 'ACCESS_URL':
|
|
395
|
+
config.accessUrl = cleanValue;
|
|
396
|
+
break;
|
|
397
|
+
case 'EGAIN_SCOPE_PREFIX':
|
|
398
|
+
config.scopePrefix = cleanValue;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
return config;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
console.error('Could not load .env file');
|
|
409
|
+
}
|
|
410
|
+
return {};
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Generate Safari warning page (Safari doesn't support private browsing via CLI)
|
|
414
|
+
*/
|
|
415
|
+
getSafariWarningPage() {
|
|
416
|
+
try {
|
|
417
|
+
const projectRoot = getProjectRoot();
|
|
418
|
+
const htmlPath = path.join(projectRoot, 'src', 'hooks', 'auth-pages', 'safari-warning.html');
|
|
419
|
+
return fs.readFileSync(htmlPath, 'utf8');
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
console.error('⚠️ Could not load Safari warning page:', error);
|
|
423
|
+
// Fallback minimal HTML
|
|
424
|
+
return '<html><body><h1>Safari Not Supported</h1><p>Safari does not support private browsing mode via command line.</p></body></html>';
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Load HTML page for browser-based configuration
|
|
429
|
+
*/
|
|
430
|
+
getConfigPage() {
|
|
431
|
+
try {
|
|
432
|
+
const projectRoot = getProjectRoot();
|
|
433
|
+
const htmlPath = path.join(projectRoot, 'src', 'hooks', 'auth-pages', 'config-page.html');
|
|
434
|
+
return fs.readFileSync(htmlPath, 'utf8');
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
console.error('⚠️ Could not load config page:', error);
|
|
438
|
+
// Fallback minimal HTML
|
|
439
|
+
return '<html><body><h1>Configuration Error</h1><p>Could not load configuration page.</p></body></html>';
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Serve JavaScript for config page
|
|
444
|
+
*/
|
|
445
|
+
getConfigPageJS() {
|
|
446
|
+
try {
|
|
447
|
+
const projectRoot = getProjectRoot();
|
|
448
|
+
const jsPath = path.join(projectRoot, 'src', 'hooks', 'auth-pages', 'config-page.js');
|
|
449
|
+
return fs.readFileSync(jsPath, 'utf8');
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
console.error('⚠️ Could not load config page JS:', error);
|
|
453
|
+
return ''; // Return empty string if JS fails to load
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
buildAuthUrl() {
|
|
457
|
+
const { environmentUrl, clientId, redirectUri, authUrl, scopePrefix } = this.authConfig;
|
|
458
|
+
console.error('🔧 Building OAuth URL...');
|
|
459
|
+
if (!authUrl) {
|
|
460
|
+
throw new Error('Authorization URL is required');
|
|
461
|
+
}
|
|
462
|
+
// Clean and validate redirect_uri (must match exactly what's registered)
|
|
463
|
+
const cleanRedirectUri = redirectUri?.trim();
|
|
464
|
+
if (!cleanRedirectUri) {
|
|
465
|
+
throw new Error('Redirect URI is required');
|
|
466
|
+
}
|
|
467
|
+
// Log the redirect URI for debugging
|
|
468
|
+
console.error('🔗 Redirect URI:', cleanRedirectUri);
|
|
469
|
+
console.error('⚠️ Make sure this EXACTLY matches the redirect URI in your client application (including trailing slashes)');
|
|
470
|
+
// Parse the authUrl to remove any existing domain_hint parameter
|
|
471
|
+
let baseUrl = authUrl;
|
|
472
|
+
let existingParams = new URLSearchParams();
|
|
473
|
+
// Check if authUrl already has query parameters
|
|
474
|
+
const urlParts = authUrl.split('?');
|
|
475
|
+
if (urlParts.length > 1 && urlParts[0]) {
|
|
476
|
+
baseUrl = urlParts[0];
|
|
477
|
+
const queryString = urlParts.slice(1).join('?'); // Handle multiple ? characters
|
|
478
|
+
existingParams = new URLSearchParams(queryString);
|
|
479
|
+
// Remove domain_hint if it exists (we'll add our own)
|
|
480
|
+
if (existingParams.has('domain_hint')) {
|
|
481
|
+
existingParams.delete('domain_hint');
|
|
482
|
+
console.error('🔧 Removed existing domain_hint from authorization URL');
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const prefix = scopePrefix || '';
|
|
486
|
+
const scope = `${prefix}knowledge.portalmgr.manage ${prefix}knowledge.portalmgr.read ${prefix}core.aiservices.read`;
|
|
487
|
+
// Add our parameters
|
|
488
|
+
existingParams.set('domain_hint', environmentUrl.trim());
|
|
489
|
+
existingParams.set('client_id', clientId.trim());
|
|
490
|
+
existingParams.set('response_type', 'code');
|
|
491
|
+
existingParams.set('redirect_uri', cleanRedirectUri);
|
|
492
|
+
existingParams.set('scope', scope);
|
|
493
|
+
existingParams.set('forceLogin', 'yes');
|
|
494
|
+
existingParams.set('prompt', 'login');
|
|
495
|
+
// Add PKCE only for public clients (no client_secret)
|
|
496
|
+
// SSO/confidential clients use client_secret only (no PKCE)
|
|
497
|
+
if (!this.authConfig.clientSecret) {
|
|
498
|
+
existingParams.set('code_challenge', this.codeChallenge);
|
|
499
|
+
existingParams.set('code_challenge_method', 'S256');
|
|
500
|
+
console.error('🔐 Using PKCE flow (public client)');
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
console.error('🔐 Using SSO/confidential client flow (no PKCE)');
|
|
504
|
+
}
|
|
505
|
+
// Reconstruct the full URL
|
|
506
|
+
const fullUrl = `${baseUrl}?${existingParams.toString()}`;
|
|
507
|
+
return fullUrl;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Monitor browser window for authorization code in URL
|
|
511
|
+
* Works with ANY redirect URL - detects when URL contains code= parameter
|
|
512
|
+
*/
|
|
513
|
+
async monitorBrowserForAuthCode() {
|
|
514
|
+
const platform = process.platform;
|
|
515
|
+
const timeout = 120; // 2 minutes
|
|
516
|
+
const startTime = Date.now();
|
|
517
|
+
if (platform === 'darwin') {
|
|
518
|
+
// macOS - Monitor using AppleScript
|
|
519
|
+
console.error(`🔍 Monitoring ${this.detectedBrowser} for authorization code...`);
|
|
520
|
+
let lastUrl = '';
|
|
521
|
+
while ((Date.now() - startTime) < timeout * 1000) {
|
|
522
|
+
try {
|
|
523
|
+
// Get URL from browser using AppleScript
|
|
524
|
+
const script = `
|
|
525
|
+
tell application "${this.detectedBrowser}"
|
|
526
|
+
try
|
|
527
|
+
set currentURL to URL of active tab of front window
|
|
528
|
+
return currentURL
|
|
529
|
+
on error
|
|
530
|
+
return ""
|
|
531
|
+
end try
|
|
532
|
+
end tell
|
|
533
|
+
`;
|
|
534
|
+
const { stdout } = await execAsync(`osascript -e '${script}'`);
|
|
535
|
+
const currentUrl = stdout.trim();
|
|
536
|
+
if (currentUrl && currentUrl !== lastUrl) {
|
|
537
|
+
lastUrl = currentUrl;
|
|
538
|
+
console.error(`🔍 Current URL: ${currentUrl}`);
|
|
539
|
+
}
|
|
540
|
+
// Check if URL contains code= parameter (regardless of domain)
|
|
541
|
+
if (currentUrl && currentUrl.includes('code=')) {
|
|
542
|
+
console.error('✅ Found authorization code in URL!');
|
|
543
|
+
// Extract the code from the URL
|
|
544
|
+
const codeMatch = currentUrl.match(/[?&]code=([^&]+)/);
|
|
545
|
+
if (codeMatch && codeMatch[1]) {
|
|
546
|
+
const code = decodeURIComponent(codeMatch[1]);
|
|
547
|
+
console.error(`🔑 Extracted authorization code (first 20 chars): ${code.substring(0, 20)}...`);
|
|
548
|
+
console.error(` Code length: ${code.length} characters`);
|
|
549
|
+
// Close the browser window immediately (non-blocking - fire and forget)
|
|
550
|
+
setImmediate(async () => {
|
|
551
|
+
try {
|
|
552
|
+
await execAsync(`osascript -e 'tell application "${this.detectedBrowser}" to close front window'`);
|
|
553
|
+
console.error('✅ Browser window closed');
|
|
554
|
+
}
|
|
555
|
+
catch (closeError) {
|
|
556
|
+
console.error('⚠️ Could not close browser window:', closeError);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
// Return code immediately without waiting for window close
|
|
560
|
+
return code;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// Also check for error parameters
|
|
564
|
+
if (currentUrl && currentUrl.includes('error=')) {
|
|
565
|
+
const errorMatch = currentUrl.match(/[?&]error=([^&]+)/);
|
|
566
|
+
const errorDescMatch = currentUrl.match(/error_description=([^&]+)/);
|
|
567
|
+
const error = errorMatch && errorMatch[1] ? decodeURIComponent(errorMatch[1]) : 'unknown_error';
|
|
568
|
+
const errorDesc = errorDescMatch && errorDescMatch[1] ? decodeURIComponent(errorDescMatch[1]) : 'No description';
|
|
569
|
+
// Throw OAuth error - this will stop monitoring but window stays open so user can see the error
|
|
570
|
+
throw new Error(`OAuth error: ${error} - ${errorDesc}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
// Re-throw OAuth errors (they should stop monitoring but window stays open)
|
|
575
|
+
if (error instanceof Error && error.message.includes('OAuth error:')) {
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
578
|
+
// Ignore AppleScript errors and continue monitoring
|
|
579
|
+
}
|
|
580
|
+
// Wait 500ms before checking again
|
|
581
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
582
|
+
}
|
|
583
|
+
throw new Error('Authentication timeout. Please try again.');
|
|
584
|
+
}
|
|
585
|
+
else if (platform === 'win32') {
|
|
586
|
+
// Windows - Monitor browser window title (contains URL in most browsers)
|
|
587
|
+
console.error(`🔍 Monitoring ${this.detectedBrowser} for authorization code...`);
|
|
588
|
+
let lastTitle = '';
|
|
589
|
+
while ((Date.now() - startTime) < timeout * 1000) {
|
|
590
|
+
try {
|
|
591
|
+
// PowerShell script to get browser window title
|
|
592
|
+
// Window titles often contain the URL or page title
|
|
593
|
+
const browserProcessName = this.detectedBrowser.replace('.exe', '');
|
|
594
|
+
const psScript = `
|
|
595
|
+
$process = Get-Process -Name "${browserProcessName}" -ErrorAction SilentlyContinue |
|
|
596
|
+
Where-Object { $_.MainWindowHandle -ne 0 } |
|
|
597
|
+
Select-Object -First 1
|
|
598
|
+
if ($process) {
|
|
599
|
+
$process.MainWindowTitle
|
|
600
|
+
}
|
|
601
|
+
`.replace(/\n\s+/g, ' ');
|
|
602
|
+
const { stdout } = await execAsync(`powershell -Command "${psScript}"`);
|
|
603
|
+
const windowTitle = stdout.trim();
|
|
604
|
+
if (windowTitle && windowTitle !== lastTitle) {
|
|
605
|
+
lastTitle = windowTitle;
|
|
606
|
+
console.error(`🔍 Browser window: ${windowTitle.substring(0, 100)}...`);
|
|
607
|
+
// Check if title or URL contains the code parameter
|
|
608
|
+
// Most browsers show URL in the title or we can detect redirect completion
|
|
609
|
+
if (windowTitle.includes('code=') || windowTitle.includes('localhost:3333')) {
|
|
610
|
+
console.error('✅ Detected OAuth callback!');
|
|
611
|
+
// Try to extract code from title if visible
|
|
612
|
+
const codeMatch = windowTitle.match(/code=([^&\s]+)/);
|
|
613
|
+
if (codeMatch && codeMatch[1]) {
|
|
614
|
+
const code = decodeURIComponent(codeMatch[1]);
|
|
615
|
+
console.error(`🔑 Extracted authorization code (first 20 chars): ${code.substring(0, 20)}...`);
|
|
616
|
+
console.error(` Code length: ${code.length} characters`);
|
|
617
|
+
// Close browser window immediately (non-blocking - fire and forget)
|
|
618
|
+
setImmediate(async () => {
|
|
619
|
+
try {
|
|
620
|
+
await execAsync(`powershell -Command "Stop-Process -Name '${browserProcessName}' -Force"`);
|
|
621
|
+
console.error('✅ Browser window closed');
|
|
622
|
+
}
|
|
623
|
+
catch (closeError) {
|
|
624
|
+
console.error('⚠️ Could not close browser window:', closeError);
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
// Return code immediately without waiting for window close
|
|
628
|
+
return code;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// Check for OAuth error in title
|
|
632
|
+
if (windowTitle.includes('error=')) {
|
|
633
|
+
const errorMatch = windowTitle.match(/error=([^&\s]+)/);
|
|
634
|
+
const error = errorMatch && errorMatch[1] ? decodeURIComponent(errorMatch[1]) : 'unknown_error';
|
|
635
|
+
// Throw OAuth error - this will stop monitoring but window stays open so user can see the error
|
|
636
|
+
throw new Error(`OAuth error: ${error}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
// Re-throw OAuth errors (they should stop monitoring but window stays open)
|
|
642
|
+
if (error instanceof Error && error.message.includes('OAuth error:')) {
|
|
643
|
+
throw error;
|
|
644
|
+
}
|
|
645
|
+
// Ignore other errors and continue monitoring
|
|
646
|
+
}
|
|
647
|
+
// Wait 500ms before checking again
|
|
648
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
649
|
+
}
|
|
650
|
+
throw new Error('Authentication timeout. The browser window title did not show the authorization code. Please ensure your redirect URL is http://localhost:3333/callback for automatic detection on Windows.');
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
throw new Error('Linux is not supported. Use macOS or Windows for automatic authentication.');
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async getUserAccessToken(code) {
|
|
657
|
+
const { clientId, clientSecret, redirectUri, accessUrl } = this.authConfig;
|
|
658
|
+
console.error('🔄 Starting token exchange...');
|
|
659
|
+
// Warn if Access Token URL doesn't look like a token endpoint
|
|
660
|
+
if (accessUrl && !accessUrl.toLowerCase().includes('token')) {
|
|
661
|
+
console.error('⚠️ WARNING: Access Token URL does not contain "token" - verify this is correct!');
|
|
662
|
+
}
|
|
663
|
+
// Temporarily disable SSL verification for development/testing
|
|
664
|
+
// This is a workaround for corporate networks or certificate issues
|
|
665
|
+
const originalRejectUnauthorized = process.env['NODE_TLS_REJECT_UNAUTHORIZED'];
|
|
666
|
+
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
|
|
667
|
+
const headers = {
|
|
668
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
669
|
+
};
|
|
670
|
+
try {
|
|
671
|
+
// SSO/Confidential client flow: Use client_secret only (no PKCE)
|
|
672
|
+
// Public client flow: Use PKCE with code_verifier (no client_secret)
|
|
673
|
+
if (clientSecret) {
|
|
674
|
+
console.error('🔄 Using SSO/confidential client flow (client_secret only, no PKCE)...');
|
|
675
|
+
const confidentialClientBody = new URLSearchParams({
|
|
676
|
+
code: code,
|
|
677
|
+
grant_type: 'authorization_code',
|
|
678
|
+
redirect_uri: redirectUri,
|
|
679
|
+
client_id: clientId,
|
|
680
|
+
client_secret: clientSecret
|
|
681
|
+
});
|
|
682
|
+
const confidentialResponse = await fetch(accessUrl, {
|
|
683
|
+
method: 'POST',
|
|
684
|
+
headers,
|
|
685
|
+
body: confidentialClientBody
|
|
686
|
+
});
|
|
687
|
+
console.error('📨 Token response received (confidential client):', confidentialResponse.status, confidentialResponse.statusText);
|
|
688
|
+
if (confidentialResponse.ok) {
|
|
689
|
+
const data = await confidentialResponse.json();
|
|
690
|
+
if (data.access_token) {
|
|
691
|
+
console.error('✅ Token received (confidential client)');
|
|
692
|
+
await this.saveTokenWithExpiration(data.access_token, data.expires_in);
|
|
693
|
+
return data.access_token;
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
throw new Error('No access_token in confidential client response');
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
const errorText = await confidentialResponse.text();
|
|
701
|
+
console.error('❌ Confidential client failed:', confidentialResponse.status);
|
|
702
|
+
throw new Error(`Token request failed: ${confidentialResponse.status} - ${errorText}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
// Public client flow: Use PKCE with code_verifier
|
|
707
|
+
console.error('🔄 Using public client flow (PKCE with code_verifier)...');
|
|
708
|
+
const publicClientBody = new URLSearchParams({
|
|
709
|
+
code: code,
|
|
710
|
+
grant_type: 'authorization_code',
|
|
711
|
+
redirect_uri: redirectUri,
|
|
712
|
+
client_id: clientId,
|
|
713
|
+
code_verifier: this.codeVerifier
|
|
714
|
+
});
|
|
715
|
+
const publicResponse = await fetch(accessUrl, {
|
|
716
|
+
method: 'POST',
|
|
717
|
+
headers,
|
|
718
|
+
body: publicClientBody
|
|
719
|
+
});
|
|
720
|
+
console.error('📨 Token response received (public client):', publicResponse.status, publicResponse.statusText);
|
|
721
|
+
if (publicResponse.ok) {
|
|
722
|
+
const data = await publicResponse.json();
|
|
723
|
+
if (data.access_token) {
|
|
724
|
+
console.error('✅ Token received (public client)');
|
|
725
|
+
await this.saveTokenWithExpiration(data.access_token, data.expires_in);
|
|
726
|
+
return data.access_token;
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
throw new Error('No access_token in public client response');
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
const errorText = await publicResponse.text();
|
|
734
|
+
console.error('❌ Public client failed:', publicResponse.status);
|
|
735
|
+
throw new Error(`Token request failed (public client): ${publicResponse.status} - ${errorText}`);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
console.error('💥 Token exchange error:', error);
|
|
741
|
+
throw new Error(`Error getting user access token: ${error}`);
|
|
742
|
+
}
|
|
743
|
+
finally {
|
|
744
|
+
// Restore original SSL setting
|
|
745
|
+
if (originalRejectUnauthorized !== undefined) {
|
|
746
|
+
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = originalRejectUnauthorized;
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
delete process.env['NODE_TLS_REJECT_UNAUTHORIZED'];
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
async saveToken(token) {
|
|
754
|
+
try {
|
|
755
|
+
// Save to the project root directory (not current working directory)
|
|
756
|
+
const projectRoot = getProjectRoot();
|
|
757
|
+
const tokenPath = path.join(projectRoot, '.bearer_token');
|
|
758
|
+
try {
|
|
759
|
+
fs.writeFileSync(tokenPath, token);
|
|
760
|
+
console.error(`💾 Access token saved to ${tokenPath}`);
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
console.error(`❌ Failed to save token to ${tokenPath}:`, error);
|
|
764
|
+
throw error;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
console.error('Could not save token:', error);
|
|
769
|
+
throw error;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Saves token with expiration metadata for fast validation
|
|
774
|
+
* @param token The bearer token
|
|
775
|
+
* @param expiresIn Expiration time in seconds (from OAuth2 response)
|
|
776
|
+
*/
|
|
777
|
+
async saveTokenWithExpiration(token, expiresIn) {
|
|
778
|
+
try {
|
|
779
|
+
const now = Date.now();
|
|
780
|
+
const expirationTime = expiresIn ? now + (expiresIn * 1000) : now + (3600 * 1000);
|
|
781
|
+
const tokenData = {
|
|
782
|
+
token,
|
|
783
|
+
expiresAt: expirationTime,
|
|
784
|
+
createdAt: now
|
|
785
|
+
};
|
|
786
|
+
const projectRoot = getProjectRoot();
|
|
787
|
+
const tokenPath = path.join(projectRoot, '.bearer_token');
|
|
788
|
+
const metadataPath = path.join(projectRoot, '.bearer_token_metadata');
|
|
789
|
+
try {
|
|
790
|
+
fs.writeFileSync(tokenPath, token);
|
|
791
|
+
fs.writeFileSync(metadataPath, JSON.stringify(tokenData, null, 2));
|
|
792
|
+
console.error(`💾 Token saved (expires: ${new Date(expirationTime).toLocaleString()})`);
|
|
793
|
+
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
await this.saveToken(token);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
catch (error) {
|
|
799
|
+
await this.saveToken(token);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Checks if the stored token is expired without making API calls
|
|
804
|
+
* @returns true if token is valid, false if expired or not found
|
|
805
|
+
*/
|
|
806
|
+
isTokenValid() {
|
|
807
|
+
try {
|
|
808
|
+
const projectRoot = getProjectRoot();
|
|
809
|
+
const metadataPath = path.join(projectRoot, '.bearer_token_metadata');
|
|
810
|
+
if (fs.existsSync(metadataPath)) {
|
|
811
|
+
const metadataContent = fs.readFileSync(metadataPath, 'utf8');
|
|
812
|
+
const tokenData = JSON.parse(metadataContent);
|
|
813
|
+
const timeUntilExpiry = tokenData.expiresAt - Date.now();
|
|
814
|
+
if (timeUntilExpiry > 60000) { // Valid if more than 1 minute left
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
console.error(`⏰ AUTH: Token expires in ${Math.round(timeUntilExpiry / 1000)} seconds - treating as expired`);
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
catch (error) {
|
|
825
|
+
return false;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// Force login mechanism removed - use `npm run logout` instead
|
|
829
|
+
loadExistingToken() {
|
|
830
|
+
try {
|
|
831
|
+
// Check in the project root directory (not current working directory)
|
|
832
|
+
const projectRoot = getProjectRoot();
|
|
833
|
+
const tokenPath = path.join(projectRoot, '.bearer_token');
|
|
834
|
+
if (fs.existsSync(tokenPath)) {
|
|
835
|
+
const token = fs.readFileSync(tokenPath, 'utf8').trim();
|
|
836
|
+
if (token && token.length > 10) { // Basic validation
|
|
837
|
+
console.error(`🔑 Found existing bearer token at: ${tokenPath}`);
|
|
838
|
+
return token;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
console.error('Could not load existing token:', error);
|
|
844
|
+
}
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Clear the stored bearer token (for remote clearing)
|
|
849
|
+
*/
|
|
850
|
+
clearToken() {
|
|
851
|
+
try {
|
|
852
|
+
const projectRoot = getProjectRoot();
|
|
853
|
+
const tokenPath = path.join(projectRoot, '.bearer_token');
|
|
854
|
+
const metadataPath = path.join(projectRoot, '.bearer_token_metadata');
|
|
855
|
+
if (fs.existsSync(tokenPath)) {
|
|
856
|
+
fs.unlinkSync(tokenPath);
|
|
857
|
+
console.error('🗑️ Deleted bearer token file');
|
|
858
|
+
}
|
|
859
|
+
if (fs.existsSync(metadataPath)) {
|
|
860
|
+
fs.unlinkSync(metadataPath);
|
|
861
|
+
console.error('🗑️ Deleted bearer token metadata file');
|
|
862
|
+
}
|
|
863
|
+
this.token = null;
|
|
864
|
+
console.error('✅ Token cleared successfully');
|
|
865
|
+
}
|
|
866
|
+
catch (error) {
|
|
867
|
+
console.error('❌ Failed to clear token:', error);
|
|
868
|
+
throw error;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Start the configuration HTTP server
|
|
873
|
+
*/
|
|
874
|
+
async startConfigServer() {
|
|
875
|
+
return new Promise((resolve, reject) => {
|
|
876
|
+
if (this.configServer) {
|
|
877
|
+
console.error('⚠️ Config server already running');
|
|
878
|
+
resolve();
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
console.error(`🌐 Starting configuration server on http://${CONFIG_SERVER_HOST}:${CONFIG_SERVER_PORT}`);
|
|
882
|
+
this.configServer = http.createServer(async (req, res) => {
|
|
883
|
+
const url = new URL(req.url, `http://${CONFIG_SERVER_HOST}:${CONFIG_SERVER_PORT}`);
|
|
884
|
+
// Serve static images
|
|
885
|
+
if (url.pathname && url.pathname.startsWith('/img/')) {
|
|
886
|
+
// Images are in the source directory, not the compiled output
|
|
887
|
+
const projectRoot = getProjectRoot();
|
|
888
|
+
const imagePath = path.join(projectRoot, 'src', 'hooks', url.pathname);
|
|
889
|
+
try {
|
|
890
|
+
const imageData = fs.readFileSync(imagePath);
|
|
891
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
892
|
+
const contentType = ext === '.png' ? 'image/png' : ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' : 'image/png';
|
|
893
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
894
|
+
res.end(imageData);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
console.error(`❌ Image not found: ${imagePath}`, error);
|
|
899
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
900
|
+
res.end('Image not found');
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
// Serve config page JavaScript
|
|
905
|
+
if (url.pathname === '/config-page.js') {
|
|
906
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
|
907
|
+
res.end(this.getConfigPageJS());
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
// Serve configuration page
|
|
911
|
+
if (url.pathname === '/' || url.pathname === '/config') {
|
|
912
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
913
|
+
res.end(this.getConfigPage());
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
// Serve Safari warning page
|
|
917
|
+
if (url.pathname === '/safari-warning') {
|
|
918
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
919
|
+
res.end(this.getSafariWarningPage());
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
// Get saved configuration
|
|
923
|
+
if (url.pathname === '/get-config' && req.method === 'GET') {
|
|
924
|
+
try {
|
|
925
|
+
// Check if we have a valid config (at least the required fields)
|
|
926
|
+
const hasValidConfig = this.authConfig.environmentUrl &&
|
|
927
|
+
this.authConfig.clientId &&
|
|
928
|
+
this.authConfig.authUrl &&
|
|
929
|
+
this.authConfig.accessUrl &&
|
|
930
|
+
this.authConfig.redirectUri;
|
|
931
|
+
if (!hasValidConfig) {
|
|
932
|
+
// No config exists
|
|
933
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
934
|
+
res.end(JSON.stringify({ config: null }));
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
// Return current authConfig (loaded from file or .env)
|
|
938
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
939
|
+
res.end(JSON.stringify({
|
|
940
|
+
config: {
|
|
941
|
+
egainUrl: this.authConfig.environmentUrl,
|
|
942
|
+
authUrl: this.authConfig.authUrl,
|
|
943
|
+
accessTokenUrl: this.authConfig.accessUrl,
|
|
944
|
+
clientId: this.authConfig.clientId,
|
|
945
|
+
redirectUrl: this.authConfig.redirectUri,
|
|
946
|
+
clientSecret: this.authConfig.clientSecret,
|
|
947
|
+
scopePrefix: this.authConfig.scopePrefix
|
|
948
|
+
}
|
|
949
|
+
}));
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
953
|
+
res.end(JSON.stringify({ success: false, error: error.message }));
|
|
954
|
+
}
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
// Get OAuth URL for saved config (used when signing in with existing config)
|
|
958
|
+
if (url.pathname === '/get-oauth-url' && req.method === 'POST') {
|
|
959
|
+
try {
|
|
960
|
+
const oauthUrl = this.buildAuthUrl();
|
|
961
|
+
console.error('🔐 Generated OAuth URL for saved configuration');
|
|
962
|
+
console.error('🔗 OAuth URL:', oauthUrl);
|
|
963
|
+
// Mark that OAuth redirect is about to happen (shorter timeout applies)
|
|
964
|
+
this.oauthRedirectStarted = true;
|
|
965
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
966
|
+
res.end(JSON.stringify({
|
|
967
|
+
success: true,
|
|
968
|
+
oauthUrl: oauthUrl
|
|
969
|
+
}));
|
|
970
|
+
// Start monitoring browser in background (for ANY redirect URL)
|
|
971
|
+
console.error('🔍 Starting browser URL monitoring for authorization code...');
|
|
972
|
+
setImmediate(async () => {
|
|
973
|
+
try {
|
|
974
|
+
const code = await this.monitorBrowserForAuthCode();
|
|
975
|
+
console.error('✅ Authorization code detected:', code.substring(0, 10) + '...');
|
|
976
|
+
const accessToken = await this.getUserAccessToken(code);
|
|
977
|
+
console.error('✅ Access token received');
|
|
978
|
+
this.token = accessToken;
|
|
979
|
+
// Trigger cache initialization if available
|
|
980
|
+
if (this.portalCacheHook) {
|
|
981
|
+
try {
|
|
982
|
+
const fakeRequest = new Request(this.authConfig.environmentUrl, {
|
|
983
|
+
headers: { 'Authorization': `Bearer ${accessToken}` }
|
|
984
|
+
});
|
|
985
|
+
await this.portalCacheHook.ensureCacheInitialized(fakeRequest);
|
|
986
|
+
}
|
|
987
|
+
catch (error) {
|
|
988
|
+
// Cache init failure is non-fatal
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
console.error('🎉 Authentication complete! Stopping config server...');
|
|
992
|
+
this.stopConfigServer();
|
|
993
|
+
}
|
|
994
|
+
catch (authError) {
|
|
995
|
+
console.error('❌ Authentication monitoring error:', authError);
|
|
996
|
+
this.stopConfigServer();
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
catch (error) {
|
|
1001
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1002
|
+
res.end(JSON.stringify({ success: false, error: error.message }));
|
|
1003
|
+
}
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
// Clear saved configuration
|
|
1007
|
+
if (url.pathname === '/clear-config' && req.method === 'POST') {
|
|
1008
|
+
try {
|
|
1009
|
+
const configPath = getConfigPath();
|
|
1010
|
+
if (fs.existsSync(configPath)) {
|
|
1011
|
+
fs.unlinkSync(configPath);
|
|
1012
|
+
console.error(`🗑️ Deleted config file: ${configPath}`);
|
|
1013
|
+
}
|
|
1014
|
+
// Clear in-memory config
|
|
1015
|
+
this.authConfig = {};
|
|
1016
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1017
|
+
res.end(JSON.stringify({ success: true, message: 'Configuration cleared' }));
|
|
1018
|
+
}
|
|
1019
|
+
catch (error) {
|
|
1020
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1021
|
+
res.end(JSON.stringify({ success: false, error: error.message }));
|
|
1022
|
+
}
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
// Handle authentication request
|
|
1026
|
+
if (url.pathname === '/authenticate' && req.method === 'POST') {
|
|
1027
|
+
let body = '';
|
|
1028
|
+
req.on('data', chunk => { body += chunk.toString(); });
|
|
1029
|
+
req.on('end', async () => {
|
|
1030
|
+
try {
|
|
1031
|
+
const config = JSON.parse(body);
|
|
1032
|
+
console.error('📝 Received configuration from browser form');
|
|
1033
|
+
// Validate URLs don't contain spaces (common mistake: pasting multiple URLs)
|
|
1034
|
+
const urlFields = [
|
|
1035
|
+
{ name: 'eGain Environment URL', value: config.egainUrl },
|
|
1036
|
+
{ name: 'Authorization URL', value: config.authUrl },
|
|
1037
|
+
{ name: 'Access Token URL', value: config.accessTokenUrl },
|
|
1038
|
+
{ name: 'Redirect URL', value: config.redirectUrl }
|
|
1039
|
+
];
|
|
1040
|
+
for (const field of urlFields) {
|
|
1041
|
+
if (field.value && field.value.includes(' ')) {
|
|
1042
|
+
const errorMsg = `❌ ${field.name} contains spaces! It looks like multiple URLs were pasted together. Please enter only ONE URL for this field.`;
|
|
1043
|
+
console.error(errorMsg);
|
|
1044
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1045
|
+
res.end(JSON.stringify({
|
|
1046
|
+
success: false,
|
|
1047
|
+
error: `${field.name} is invalid - contains multiple URLs. Please enter only one URL per field.`
|
|
1048
|
+
}));
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
// Update authConfig from browser form data
|
|
1053
|
+
this.authConfig = {
|
|
1054
|
+
environmentUrl: config.egainUrl,
|
|
1055
|
+
authUrl: config.authUrl,
|
|
1056
|
+
accessUrl: config.accessTokenUrl,
|
|
1057
|
+
clientId: config.clientId,
|
|
1058
|
+
redirectUri: config.redirectUrl,
|
|
1059
|
+
clientSecret: config.clientSecret || undefined,
|
|
1060
|
+
scopePrefix: config.scopePrefix || undefined
|
|
1061
|
+
};
|
|
1062
|
+
// Save config to secure file storage (home directory)
|
|
1063
|
+
try {
|
|
1064
|
+
this.saveConfigToFile(this.authConfig);
|
|
1065
|
+
console.error('✅ Configuration saved to secure file storage');
|
|
1066
|
+
}
|
|
1067
|
+
catch (error) {
|
|
1068
|
+
console.error('⚠️ Failed to save config to file:', error);
|
|
1069
|
+
// Continue with authentication even if file save fails
|
|
1070
|
+
}
|
|
1071
|
+
// Generate OAuth URL and return it to frontend for redirect (single window flow)
|
|
1072
|
+
const oauthUrl = this.buildAuthUrl();
|
|
1073
|
+
console.error('🔐 Generated OAuth URL for browser redirect');
|
|
1074
|
+
console.error('🔗 OAuth URL:', oauthUrl);
|
|
1075
|
+
// Mark that OAuth redirect is about to happen (shorter timeout applies)
|
|
1076
|
+
this.oauthRedirectStarted = true;
|
|
1077
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1078
|
+
res.end(JSON.stringify({
|
|
1079
|
+
success: true,
|
|
1080
|
+
message: 'Configuration saved. Redirecting to login...',
|
|
1081
|
+
oauthUrl: oauthUrl // Frontend will redirect to this URL
|
|
1082
|
+
}));
|
|
1083
|
+
// Start monitoring browser in background (for ANY redirect URL)
|
|
1084
|
+
console.error('🔍 Starting browser URL monitoring for authorization code...');
|
|
1085
|
+
setImmediate(async () => {
|
|
1086
|
+
try {
|
|
1087
|
+
const code = await this.monitorBrowserForAuthCode();
|
|
1088
|
+
console.error('✅ Authorization code detected:', code.substring(0, 10) + '...');
|
|
1089
|
+
const accessToken = await this.getUserAccessToken(code);
|
|
1090
|
+
console.error('✅ Access token received');
|
|
1091
|
+
this.token = accessToken;
|
|
1092
|
+
// Trigger cache initialization if available
|
|
1093
|
+
if (this.portalCacheHook) {
|
|
1094
|
+
console.error('🔄 Triggering cache initialization...');
|
|
1095
|
+
try {
|
|
1096
|
+
const fakeRequest = new Request(this.authConfig.environmentUrl, {
|
|
1097
|
+
headers: { 'Authorization': `Bearer ${accessToken}` }
|
|
1098
|
+
});
|
|
1099
|
+
await this.portalCacheHook.ensureCacheInitialized(fakeRequest);
|
|
1100
|
+
console.error('✅ Cache initialization completed');
|
|
1101
|
+
}
|
|
1102
|
+
catch (error) {
|
|
1103
|
+
console.error('⚠️ Cache initialization failed:', error);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
console.error('🎉 Authentication complete! Stopping config server...');
|
|
1107
|
+
this.stopConfigServer();
|
|
1108
|
+
}
|
|
1109
|
+
catch (authError) {
|
|
1110
|
+
console.error('❌ Authentication monitoring error:', authError);
|
|
1111
|
+
this.stopConfigServer();
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
catch (error) {
|
|
1116
|
+
console.error('❌ Error processing authentication request:', error);
|
|
1117
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1118
|
+
res.end(JSON.stringify({
|
|
1119
|
+
success: false,
|
|
1120
|
+
error: error.message
|
|
1121
|
+
}));
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
// Handle OAuth callback (after user completes Azure B2C login)
|
|
1127
|
+
if (url.pathname === '/callback' && req.method === 'GET') {
|
|
1128
|
+
try {
|
|
1129
|
+
const code = url.searchParams.get('code');
|
|
1130
|
+
const error = url.searchParams.get('error');
|
|
1131
|
+
if (error) {
|
|
1132
|
+
const errorDesc = url.searchParams.get('error_description') || 'No description';
|
|
1133
|
+
console.error('❌ OAuth error:', error, errorDesc);
|
|
1134
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1135
|
+
res.end(`
|
|
1136
|
+
<html>
|
|
1137
|
+
<head><title>Authentication Error</title></head>
|
|
1138
|
+
<body style="font-family: sans-serif; padding: 40px; text-align: center;">
|
|
1139
|
+
<h1>❌ Authentication Error</h1>
|
|
1140
|
+
<p><strong>${error}</strong></p>
|
|
1141
|
+
<p>${errorDesc}</p>
|
|
1142
|
+
<p>You can close this window and try again.</p>
|
|
1143
|
+
<script>setTimeout(() => window.close(), 3000);</script>
|
|
1144
|
+
</body>
|
|
1145
|
+
</html>
|
|
1146
|
+
`);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (!code) {
|
|
1150
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
1151
|
+
res.end('Missing authorization code');
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
console.error('✅ Authorization code received from OAuth callback');
|
|
1155
|
+
// Exchange code for token (async in background)
|
|
1156
|
+
setImmediate(async () => {
|
|
1157
|
+
try {
|
|
1158
|
+
const accessToken = await this.getUserAccessToken(code);
|
|
1159
|
+
console.error('✅ Access token received');
|
|
1160
|
+
this.token = accessToken;
|
|
1161
|
+
// Trigger cache initialization if available
|
|
1162
|
+
if (this.portalCacheHook) {
|
|
1163
|
+
try {
|
|
1164
|
+
const fakeRequest = new Request(this.authConfig.environmentUrl, {
|
|
1165
|
+
headers: { 'Authorization': `Bearer ${accessToken}` }
|
|
1166
|
+
});
|
|
1167
|
+
await this.portalCacheHook.ensureCacheInitialized(fakeRequest);
|
|
1168
|
+
}
|
|
1169
|
+
catch (error) {
|
|
1170
|
+
// Cache init failure is non-fatal
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
console.error('🎉 Authentication complete! Stopping config server...');
|
|
1174
|
+
// Stop server after successful authentication
|
|
1175
|
+
this.stopConfigServer();
|
|
1176
|
+
}
|
|
1177
|
+
catch (authError) {
|
|
1178
|
+
console.error('❌ Token exchange error:', authError);
|
|
1179
|
+
this.stopConfigServer();
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
// Send success page to browser
|
|
1183
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1184
|
+
res.end(`
|
|
1185
|
+
<html>
|
|
1186
|
+
<head><title>Authentication Complete</title></head>
|
|
1187
|
+
<body style="font-family: sans-serif; padding: 40px; text-align: center;">
|
|
1188
|
+
<h1>✅ Authentication Complete!</h1>
|
|
1189
|
+
<p>You can now close this window.</p>
|
|
1190
|
+
<p style="color: #666; font-size: 14px;">This window will close automatically in 2 seconds...</p>
|
|
1191
|
+
<script>setTimeout(() => window.close(), 2000);</script>
|
|
1192
|
+
</body>
|
|
1193
|
+
</html>
|
|
1194
|
+
`);
|
|
1195
|
+
}
|
|
1196
|
+
catch (error) {
|
|
1197
|
+
console.error('❌ Error handling OAuth callback:', error);
|
|
1198
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1199
|
+
res.end(`
|
|
1200
|
+
<html>
|
|
1201
|
+
<head><title>Error</title></head>
|
|
1202
|
+
<body style="font-family: sans-serif; padding: 40px; text-align: center;">
|
|
1203
|
+
<h1>❌ Error</h1>
|
|
1204
|
+
<p>${error.message}</p>
|
|
1205
|
+
<script>setTimeout(() => window.close(), 3000);</script>
|
|
1206
|
+
</body>
|
|
1207
|
+
</html>
|
|
1208
|
+
`);
|
|
1209
|
+
}
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
// Handle clear token request
|
|
1213
|
+
if (url.pathname === '/clear-token' && req.method === 'POST') {
|
|
1214
|
+
try {
|
|
1215
|
+
this.clearToken();
|
|
1216
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1217
|
+
res.end(JSON.stringify({ success: true, message: 'Token cleared' }));
|
|
1218
|
+
}
|
|
1219
|
+
catch (error) {
|
|
1220
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1221
|
+
res.end(JSON.stringify({ success: false, error: error.message }));
|
|
1222
|
+
}
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
// Handle authentication cancellation
|
|
1226
|
+
if (url.pathname === '/cancel' && req.method === 'POST') {
|
|
1227
|
+
console.error('🚫 User cancelled authentication');
|
|
1228
|
+
this.authCancelled = true;
|
|
1229
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1230
|
+
res.end(JSON.stringify({ success: true, message: 'Authentication cancelled' }));
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
// 404
|
|
1234
|
+
res.writeHead(404);
|
|
1235
|
+
res.end('Not Found');
|
|
1236
|
+
});
|
|
1237
|
+
this.configServer.listen(CONFIG_SERVER_PORT, CONFIG_SERVER_HOST, () => {
|
|
1238
|
+
console.error(`✅ Configuration server started at http://${CONFIG_SERVER_HOST}:${CONFIG_SERVER_PORT}`);
|
|
1239
|
+
resolve();
|
|
1240
|
+
});
|
|
1241
|
+
this.configServer.on('error', (error) => {
|
|
1242
|
+
if (error.code === 'EADDRINUSE') {
|
|
1243
|
+
console.error(`❌ Port ${CONFIG_SERVER_PORT} is already in use`);
|
|
1244
|
+
reject(new Error(`Port ${CONFIG_SERVER_PORT} is already in use. Please close other applications using this port.`));
|
|
1245
|
+
}
|
|
1246
|
+
else {
|
|
1247
|
+
console.error('❌ Server error:', error);
|
|
1248
|
+
reject(error);
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Stop the configuration HTTP server
|
|
1255
|
+
*/
|
|
1256
|
+
stopConfigServer() {
|
|
1257
|
+
if (this.configServer) {
|
|
1258
|
+
console.error('🚪 Stopping configuration server...');
|
|
1259
|
+
this.configServer.close(() => {
|
|
1260
|
+
console.error('✅ Configuration server stopped');
|
|
1261
|
+
});
|
|
1262
|
+
// Immediately set to null to prevent double-close
|
|
1263
|
+
this.configServer = null;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Open browser to configuration page
|
|
1268
|
+
*/
|
|
1269
|
+
async openConfigBrowser() {
|
|
1270
|
+
const platform = process.platform;
|
|
1271
|
+
// Detect browser on macOS or Windows before opening
|
|
1272
|
+
if (platform === 'darwin' || platform === 'win32') {
|
|
1273
|
+
await this.detectDefaultBrowser();
|
|
1274
|
+
}
|
|
1275
|
+
console.error(`🌐 Browser: ${this.detectedBrowser}`);
|
|
1276
|
+
// Safari shows warning page instead of config form (security limitation)
|
|
1277
|
+
const configUrl = this.detectedBrowser === 'Safari'
|
|
1278
|
+
? `http://${CONFIG_SERVER_HOST}:${CONFIG_SERVER_PORT}/safari-warning`
|
|
1279
|
+
: `http://${CONFIG_SERVER_HOST}:${CONFIG_SERVER_PORT}/config`;
|
|
1280
|
+
if (this.detectedBrowser === 'Safari') {
|
|
1281
|
+
console.error('⚠️ Safari detected - showing browser installation guide...');
|
|
1282
|
+
}
|
|
1283
|
+
else {
|
|
1284
|
+
console.error('📋 Opening browser for configuration...');
|
|
1285
|
+
}
|
|
1286
|
+
const windowWidth = 600;
|
|
1287
|
+
const windowHeight = 800;
|
|
1288
|
+
try {
|
|
1289
|
+
if (platform === 'darwin') {
|
|
1290
|
+
// macOS - Open default browser in private mode
|
|
1291
|
+
const incognitoFlag = this.getIncognitoFlag();
|
|
1292
|
+
console.error(`🕵️ Using private mode flag: ${incognitoFlag || '(none - browser limitation)'}`);
|
|
1293
|
+
// Safari doesn't support --args flags well, use AppleScript for private browsing
|
|
1294
|
+
if (this.detectedBrowser === 'Safari') {
|
|
1295
|
+
console.error(` Opening Safari in Private Browsing mode via AppleScript...`);
|
|
1296
|
+
// AppleScript to open Safari in private browsing mode
|
|
1297
|
+
const appleScript = `
|
|
1298
|
+
tell application "Safari"
|
|
1299
|
+
activate
|
|
1300
|
+
|
|
1301
|
+
-- Try to open a new private window
|
|
1302
|
+
-- Note: This may not work in newer macOS versions due to security restrictions
|
|
1303
|
+
try
|
|
1304
|
+
make new document
|
|
1305
|
+
delay 0.5
|
|
1306
|
+
set URL of document 1 to "${configUrl}"
|
|
1307
|
+
|
|
1308
|
+
-- Attempt to enable private browsing (may not work in all macOS versions)
|
|
1309
|
+
tell window 1
|
|
1310
|
+
try
|
|
1311
|
+
set private browsing to true
|
|
1312
|
+
end try
|
|
1313
|
+
end tell
|
|
1314
|
+
on error
|
|
1315
|
+
-- Fallback: just open the URL in a new window
|
|
1316
|
+
open location "${configUrl}"
|
|
1317
|
+
end try
|
|
1318
|
+
end tell
|
|
1319
|
+
`.replace(/\n\s+/g, ' ');
|
|
1320
|
+
try {
|
|
1321
|
+
await execAsync(`osascript -e '${appleScript}'`);
|
|
1322
|
+
console.error(`⚠️ IMPORTANT: If Safari didn't open in Private Browsing mode:`);
|
|
1323
|
+
console.error(` 1. Close the Safari window`);
|
|
1324
|
+
console.error(` 2. Open Safari manually in Private Browsing (File > New Private Window)`);
|
|
1325
|
+
console.error(` 3. Navigate to: ${configUrl}`);
|
|
1326
|
+
}
|
|
1327
|
+
catch (error) {
|
|
1328
|
+
// Fallback to simple open if AppleScript fails
|
|
1329
|
+
console.error(` AppleScript failed, using fallback method...`);
|
|
1330
|
+
await execAsync(`open -a "Safari" "${configUrl}"`);
|
|
1331
|
+
console.error(`⚠️ IMPORTANT: Please manually enable Private Browsing in Safari!`);
|
|
1332
|
+
console.error(` (File > New Private Window, then go to: ${configUrl})`);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
else if (this.detectedBrowser === 'Firefox') {
|
|
1336
|
+
// Firefox: Use AppleScript to ensure private window opens even if Firefox is already running
|
|
1337
|
+
console.error(` Opening Firefox in private browsing mode...`);
|
|
1338
|
+
const firefoxScript = `
|
|
1339
|
+
tell application "Firefox"
|
|
1340
|
+
activate
|
|
1341
|
+
|
|
1342
|
+
-- Check if Firefox is running
|
|
1343
|
+
set isRunning to true
|
|
1344
|
+
|
|
1345
|
+
-- Open new private window using Firefox's built-in command
|
|
1346
|
+
try
|
|
1347
|
+
-- Use Firefox's internal private browsing command
|
|
1348
|
+
do shell script "open -a Firefox --args --private-window '${configUrl}'"
|
|
1349
|
+
on error
|
|
1350
|
+
-- Fallback: try to open with just the private flag
|
|
1351
|
+
do shell script "open -na Firefox --args -private-window '${configUrl}'"
|
|
1352
|
+
end try
|
|
1353
|
+
end tell
|
|
1354
|
+
`.replace(/\n\s+/g, ' ');
|
|
1355
|
+
try {
|
|
1356
|
+
await execAsync(`osascript -e '${firefoxScript}'`);
|
|
1357
|
+
}
|
|
1358
|
+
catch (error) {
|
|
1359
|
+
// Ultimate fallback: force new instance with -n flag
|
|
1360
|
+
console.error(` AppleScript failed, trying fallback...`);
|
|
1361
|
+
await execAsync(`open -na "Firefox" --args -private-window "${configUrl}"`);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
else {
|
|
1365
|
+
// Chrome, Edge, Brave, etc. support --args flags
|
|
1366
|
+
const args = incognitoFlag
|
|
1367
|
+
? `--args ${incognitoFlag} --app="${configUrl}" --window-size=${windowWidth},${windowHeight} --window-position=100,100`
|
|
1368
|
+
: `"${configUrl}"`; // Fallback for browsers without good CLI support
|
|
1369
|
+
const command = `open -n -a "${this.detectedBrowser}" ${args}`;
|
|
1370
|
+
console.error(` Executing: open -n -a "${this.detectedBrowser}" ...`);
|
|
1371
|
+
await execAsync(command);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
else if (platform === 'win32') {
|
|
1375
|
+
// Windows - Open detected browser with private mode
|
|
1376
|
+
const incognitoFlag = this.getIncognitoFlag();
|
|
1377
|
+
console.error(`🕵️ Using private mode flag: ${incognitoFlag}`);
|
|
1378
|
+
if (this.detectedBrowser === 'firefox') {
|
|
1379
|
+
// Firefox on Windows needs special handling
|
|
1380
|
+
console.error(` Executing: start firefox ${incognitoFlag} ...`);
|
|
1381
|
+
await execAsync(`cmd /c start "" "${this.detectedBrowser}" ${incognitoFlag} "${configUrl}"`);
|
|
1382
|
+
}
|
|
1383
|
+
else {
|
|
1384
|
+
// Chrome, Edge, Brave support --app mode
|
|
1385
|
+
console.error(` Executing: start ${this.detectedBrowser} ${incognitoFlag} --app ...`);
|
|
1386
|
+
await execAsync(`cmd /c start "" "${this.detectedBrowser}" ${incognitoFlag} --app="${configUrl}" --window-size=${windowWidth},${windowHeight} --window-position=100,100`);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
else {
|
|
1390
|
+
// Linux - Use default browser
|
|
1391
|
+
console.error(` Executing: xdg-open ...`);
|
|
1392
|
+
await execAsync(`xdg-open "${configUrl}"`);
|
|
1393
|
+
}
|
|
1394
|
+
console.error('✅ Browser opened successfully');
|
|
1395
|
+
}
|
|
1396
|
+
catch (error) {
|
|
1397
|
+
console.error('❌ Failed to open browser:', error instanceof Error ? error.message : error);
|
|
1398
|
+
console.error('⚠️ Please open manually: ' + configUrl);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
async authenticate() {
|
|
1402
|
+
try {
|
|
1403
|
+
// Try to use existing token first (lazy mode)
|
|
1404
|
+
console.error('🔐 Starting Azure B2C OAuth2 authentication...');
|
|
1405
|
+
// Fast validation using metadata (no API calls)
|
|
1406
|
+
if (this.isTokenValid()) {
|
|
1407
|
+
const existingToken = this.loadExistingToken();
|
|
1408
|
+
if (existingToken) {
|
|
1409
|
+
console.error('🎉 Using existing valid bearer token (lazy mode)');
|
|
1410
|
+
this.token = existingToken;
|
|
1411
|
+
// Trigger cache initialization with existing token
|
|
1412
|
+
if (this.portalCacheHook) {
|
|
1413
|
+
try {
|
|
1414
|
+
const fakeRequest = new Request(this.authConfig.environmentUrl || 'https://api-dev9.knowledge.ai/knowledge', {
|
|
1415
|
+
headers: { 'Authorization': `Bearer ${existingToken}` }
|
|
1416
|
+
});
|
|
1417
|
+
await this.portalCacheHook.ensureCacheInitialized(fakeRequest);
|
|
1418
|
+
}
|
|
1419
|
+
catch (error) {
|
|
1420
|
+
// Cache init failure is non-fatal
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return this.token;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
else {
|
|
1427
|
+
console.error('⏰ Existing token is expired or not found, proceeding with fresh login...');
|
|
1428
|
+
}
|
|
1429
|
+
// Check if we have configuration (from .env or file)
|
|
1430
|
+
const hasConfig = this.authConfig.environmentUrl && this.authConfig.clientId &&
|
|
1431
|
+
this.authConfig.redirectUri && this.authConfig.authUrl && this.authConfig.accessUrl;
|
|
1432
|
+
if (!hasConfig) {
|
|
1433
|
+
console.error('📝 No configuration found, starting browser-based configuration...');
|
|
1434
|
+
}
|
|
1435
|
+
else {
|
|
1436
|
+
console.error('✅ Configuration found, opening browser for authentication...');
|
|
1437
|
+
}
|
|
1438
|
+
console.error('🌐 A browser window will open for you to authenticate');
|
|
1439
|
+
// Start config server and open browser (for both cases)
|
|
1440
|
+
await this.startConfigServer();
|
|
1441
|
+
await this.openConfigBrowser();
|
|
1442
|
+
// Two-phase timeout:
|
|
1443
|
+
// Phase 1: Filling config form (generous time)
|
|
1444
|
+
// Phase 2: After OAuth redirect, logging in (shorter time)
|
|
1445
|
+
const configFormTimeout = 900000; // 15 minutes for filling form
|
|
1446
|
+
const oauthLoginTimeout = 300000; // 5 minutes after OAuth redirect
|
|
1447
|
+
const checkInterval = 1000; // 1 second
|
|
1448
|
+
const startTime = Date.now();
|
|
1449
|
+
while (!this.token && !this.authCancelled) {
|
|
1450
|
+
const elapsed = Date.now() - startTime;
|
|
1451
|
+
// Determine which timeout applies
|
|
1452
|
+
const currentTimeout = this.oauthRedirectStarted ? oauthLoginTimeout : configFormTimeout;
|
|
1453
|
+
const timeoutLabel = this.oauthRedirectStarted ? 'OAuth login' : 'configuration form';
|
|
1454
|
+
if (elapsed >= currentTimeout) {
|
|
1455
|
+
this.stopConfigServer();
|
|
1456
|
+
throw new Error(`Authentication timeout (${timeoutLabel}). Please try again.`);
|
|
1457
|
+
}
|
|
1458
|
+
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
1459
|
+
}
|
|
1460
|
+
if (this.authCancelled) {
|
|
1461
|
+
this.stopConfigServer();
|
|
1462
|
+
this.authCancelled = false; // Reset for next attempt
|
|
1463
|
+
this.oauthRedirectStarted = false; // Reset flag
|
|
1464
|
+
throw new Error('Authentication cancelled by user. Authentication is required to use eGain MCP tools.');
|
|
1465
|
+
}
|
|
1466
|
+
// Final check: ensure token was actually received
|
|
1467
|
+
if (!this.token) {
|
|
1468
|
+
this.stopConfigServer();
|
|
1469
|
+
throw new Error('Authentication completed but no token received. Please try again.');
|
|
1470
|
+
}
|
|
1471
|
+
console.error('✅ Authentication completed');
|
|
1472
|
+
return this.token;
|
|
1473
|
+
}
|
|
1474
|
+
catch (error) {
|
|
1475
|
+
this.stopConfigServer();
|
|
1476
|
+
console.error('❌ Authentication failed:', error);
|
|
1477
|
+
throw error;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
async beforeRequest(_hookCtx, request) {
|
|
1481
|
+
// Check if request already has valid Authorization header
|
|
1482
|
+
const existingAuth = request.headers.get('Authorization');
|
|
1483
|
+
if (existingAuth && existingAuth.startsWith('Bearer ') && this.isTokenValid()) {
|
|
1484
|
+
return request;
|
|
1485
|
+
}
|
|
1486
|
+
// Get or refresh token
|
|
1487
|
+
if (!this.token || !this.isTokenValid()) {
|
|
1488
|
+
this.token = await this.authenticate();
|
|
1489
|
+
}
|
|
1490
|
+
// Clone the request and add the Authorization header
|
|
1491
|
+
const headers = new Headers(request.headers);
|
|
1492
|
+
headers.set('Authorization', `Bearer ${this.token}`);
|
|
1493
|
+
const requestOptions = {
|
|
1494
|
+
method: request.method,
|
|
1495
|
+
headers: headers,
|
|
1496
|
+
signal: request.signal,
|
|
1497
|
+
};
|
|
1498
|
+
if (request.body) {
|
|
1499
|
+
requestOptions.body = request.body;
|
|
1500
|
+
requestOptions.duplex = 'half';
|
|
1501
|
+
}
|
|
1502
|
+
return new Request(request.url, requestOptions);
|
|
1503
|
+
}
|
|
1504
|
+
sdkInit(opts) {
|
|
1505
|
+
// Check if bearer token was provided via CLI flag
|
|
1506
|
+
if (opts.security && typeof opts.security === 'object' && 'bearerAuth' in opts.security) {
|
|
1507
|
+
const providedToken = opts.security.bearerAuth;
|
|
1508
|
+
if (providedToken && typeof providedToken === 'string' && providedToken.trim().length > 0) {
|
|
1509
|
+
console.error('🔑 Using bearer token from CLI flag');
|
|
1510
|
+
this.token = providedToken;
|
|
1511
|
+
return opts;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
// Load existing token if available
|
|
1515
|
+
if (!this.token) {
|
|
1516
|
+
this.token = this.loadExistingToken();
|
|
1517
|
+
}
|
|
1518
|
+
// If we have a token, set up the security provider
|
|
1519
|
+
if (this.token) {
|
|
1520
|
+
return {
|
|
1521
|
+
...opts,
|
|
1522
|
+
security: { accessToken: this.token }
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
// No token available - set up async authentication
|
|
1526
|
+
const securityProvider = async () => {
|
|
1527
|
+
const token = await this.authenticate();
|
|
1528
|
+
return { accessToken: token };
|
|
1529
|
+
};
|
|
1530
|
+
return {
|
|
1531
|
+
...opts,
|
|
1532
|
+
security: securityProvider,
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
findProjectRoot() {
|
|
1536
|
+
const roots = [];
|
|
1537
|
+
// Always prioritize process.cwd() as the primary project root
|
|
1538
|
+
const cwd = process.cwd();
|
|
1539
|
+
const cwdPackageJsonPath = path.join(cwd, 'package.json');
|
|
1540
|
+
if (fs.existsSync(cwdPackageJsonPath)) {
|
|
1541
|
+
roots.push(cwd);
|
|
1542
|
+
// Only return the current working directory to ensure we stay within the project
|
|
1543
|
+
return roots;
|
|
1544
|
+
}
|
|
1545
|
+
// If no package.json in cwd, search up from __dirname but limit to reasonable project boundaries
|
|
1546
|
+
let currentDir = __dirname;
|
|
1547
|
+
const maxLevelsUp = 5; // Reasonable limit to prevent going too far up
|
|
1548
|
+
for (let i = 0; i < maxLevelsUp; i++) {
|
|
1549
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
1550
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1551
|
+
// Only add if it's not already in roots and seems to be within a reasonable project structure
|
|
1552
|
+
if (!roots.includes(currentDir)) {
|
|
1553
|
+
roots.push(currentDir);
|
|
1554
|
+
}
|
|
1555
|
+
break;
|
|
1556
|
+
}
|
|
1557
|
+
const parentDir = path.dirname(currentDir);
|
|
1558
|
+
if (parentDir === currentDir)
|
|
1559
|
+
break; // Reached filesystem root
|
|
1560
|
+
currentDir = parentDir;
|
|
1561
|
+
}
|
|
1562
|
+
return roots;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
//# sourceMappingURL=auth-hook.js.map
|