@thoughtspot/mcp-auth 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,127 @@
1
+ ThoughtSpot Development Tools End User License Agreement
2
+
3
+ THIS THOUGHTSPOT DEVELOPMENT TOOLS END USER LICENSE AGREEMENT ("EULA") FORMS A BINDING AGREEMENT BETWEEN YOU INDIVIDUALLY OR THE BUSINESS ENTITY OR PUBLIC AGENCY ON WHOSE BEHALF YOU ARE ACCEPTING THIS AGREEMENT ("COMPANY") AND THOUGHTSPOT, INC. ("THOUGHTSPOT").
4
+
5
+ THIS EULA DESCRIBES THE RIGHTS AND OBLIGATIONS OF COMPANY AND THOUGHTSPOT GOVERNING THE USE OF ANY APPLICATION PROGRAMMING INTERFACE, CONNECTOR, SOFTWARE DEVELOPMENT KIT, CODE SNIPPET, SAMPLE CODE, FREE CUSTOM USER INTERFACE OR VISUALIZATION, SAMPLE DATA, AND THE CORRESPONDING DOCUMENTATION FOR EACH, ANY UPDATES AND UPGRADES THERETO, AND ANY MODIFICATIONS, ENHANCEMENTS, OR IMPROVEMENTS, OF ANY OF THE FOREGOING, MADE AVAILABLE BY THOUGHTSPOT TOGETHER WITH THIS EULA (EACH A "TOOL" AND COLLECTIVELY "TOOLS").
6
+
7
+ THIS EULA IS ACCEPTED BY:
8
+ (1) INDICATING ACCEPTANCE OF THESE TERMS BY CLICKING "SUBMIT," "ACCEPT" OR A SIMILAR BUTTON WHEN THIS EULA IS REFERENCED ON A WEB PAGE TO RECEIVE A TOOL; OR
9
+ (2) DOWNLOADING, INSTALLING, OR USING, ANY PORTION OF THE TOOL.
10
+
11
+ THE INDIVIDUAL ACCEPTING THIS AGREEMENT ON BEHALF OF COMPANY REPRESENTS AND WARRANTS THAT HE OR SHE:
12
+ (A) IS AN EMPLOYEE, CONTRACTOR, OR AGENT OF, AND HAS THE AUTHORITY TO REPRESENT, COMPANY; AND
13
+ (B) HAS READ AND UNDERSTANDS ALL THE PROVISIONS OF THIS AGREEMENT.
14
+
15
+ IF COMPANY DOES NOT WISH TO ACCEPT THIS AGREEMENT, OR THE INDIVIDUAL ACCEPTING THE AGREEMENT DOES NOT HAVE AUTHORITY TO BIND COMPANY TO THIS AGREEMENT, THEN DO NOT CLICK OR SIGN TO ACCEPT THIS AGREEMENT, OR DOWNLOAD, INSTALL, OR USE, ANY TOOL.
16
+
17
+ In the event of any conflict between the terms of this EULA and a signed agreement between Company and ThoughtSpot, the terms of the signed agreement will apply.
18
+
19
+ ACKNOWLEDGMENT
20
+ ThoughtSpot provides the Tool to Company "as is" and "as available" and as an accommodation to Company to more quickly connect third-party software or services with, utilize sample data or code on, or utilize sample data sets or other materials with, the search and analytics ThoughtSpot Cloud subscription service or ThoughtSpot Application licensed software, to which Company must have purchased the necessary use rights pursuant to a separate purchase agreement ("Agreement").
21
+
22
+ ThoughtSpot may at any time remove Company's access to any Tool or terminate the availability of a Tool without any liability to Company or any third party. In the event of termination, Company must remove and destroy all copies of the affected Tool, including all backup copies from all devices Company owns, possesses or controls and on which the Tool is installed.
23
+
24
+ LICENSE SCOPE
25
+ Subject to Company's compliance with this EULA, ThoughtSpot hereby grants to Company a royalty-free, sub-licensable, transferable, non-exclusive, worldwide right and license to use, reproduce, display, perform, import and export, the Tool. The Tool is licensed, not sold.
26
+
27
+ RESTRICTIONS
28
+ Company will not (and has no license to):
29
+ (a) use the Tool except as permitted in this EULA;
30
+ (b) sell, resell, license, sublicense, rent, lease, encumber, lend, distribute, transfer, or provide a third party with access to the Tool;
31
+ (c) modify, or create derivative works of the Tool;
32
+ (d) circumvent or remove by any means any click-accept or copy protection used by ThoughtSpot in connection with the Tool;
33
+ (e) use the Tool to conduct competitive research, to develop a product that is competitive with any ThoughtSpot product offering, or otherwise if Company is a competitor to ThoughtSpot, or to assert, authorize, assist, or encourage a third-party to assert, against ThoughtSpot or any of its affiliates, customers, vendors, business partners, or licensors, any patent or other intellectual property claim regarding ThoughtSpot products or services;
34
+ (f) publicly disseminate any performance or security vulnerability test (including a penetration test) results or analysis related to or derived from the Tool;
35
+ (g) use the Tool to create a product that converts ThoughtSpot products' file formats for use with data analysis, machine learning, or data visualization software that is not the property of ThoughtSpot;
36
+ (h) use the Tool to access ThoughtSpot products in a manner not authorized by the Agreement or this EULA;
37
+ (i) use the Tool in any manner that violates any applicable laws or regulations; or
38
+ (j) to the extent that the Tool includes a third-party application programming interface, then Company will not modify, distribute, or use, the Tool with anything other than to connect the intended corresponding third-party technology to ThoughtSpot's products.
39
+
40
+ Company will not cause, encourage, or permit any other person or entity under its control from taking any actions that Company is prohibited from taking under this Agreement.
41
+
42
+ Before Company engages in any of the foregoing acts that it believes it may be entitled to, it will provide ThoughtSpot with 30 days' prior notice to legal@thoughtspot.com, and provide reasonably requested information to allow ThoughtSpot to assess Company's claim. ThoughtSpot may, in its discretion, provide alternatives that reduce adverse impacts to ThoughtSpot's intellectual property or other rights.
43
+
44
+ OPEN SOURCE COMPONENTS
45
+ A Tool may include one or more open source software components provided under separate license terms which can be found in the open source disclosure file provided with or within a Tool download.
46
+
47
+ Notwithstanding anything herein to the contrary, open source software is licensed to Company under such OSS's own applicable license terms, which can be found in the attribution file. The open source license terms are consistent with the license granted in this EULA, and may contain additional rights benefiting Company. The open source license terms shall take precedence over this EULA to the extent that this EULA imposes greater restrictions on Company than the applicable open source license terms.
48
+
49
+ To the extent the license for any open source software requires ThoughtSpot to make available to Company the corresponding source code and/or modifications, Company may obtain a copy of the applicable source files by sending a written request, with Company's name and address to:
50
+ ThoughtSpot, Inc.
51
+ 444 Castro Street, Suite 1000
52
+ Mountain View, CA 94041
53
+ United States of America
54
+
55
+ All requests should clearly specify: Open Source Files Request, Attention: General Counsel. This offer to obtain a copy of such source files is valid for three years from the date Company acquired the applicable Tool.
56
+
57
+ THIRD-PARTY COMPONENTS
58
+ In addition to open source components, a Tool may include one or more components licensed by a third party (a "Component") (e.g., an application programming interface or a sample data set).
59
+
60
+ To the extent that third-party license requirements apply to a third-party component in a Tool, such additional license terms will be provided in the open source disclosure file provided with or within a Tool download. Third-party license terms shall take precedence over this EULA to the extent that they impose additional restrictions or limitations on Company than the license provided herein for download and use of a Component.
61
+
62
+ The parties agree that:
63
+ (a) to the extent that the terms between Company and the third party for use of a third-party technology accessed by the Component are more restrictive, such terms shall apply to Company's use of the Component;
64
+ (b) ThoughtSpot, and not the third party, is responsible for the Component including, without limitation, for any warranties, maintenance, and support thereof, and the third party does not warrant the Component's accuracy, reliability, completeness, usefulness, non-infringement, or quality of the Component, and will not be liable for any losses or damages of any kind, including lost profits or other indirect or consequential damages, relating to use of or reliance on the Component; and
65
+ (c) the third party owns all right, title, and interest in and to the Component, including all intellectual property rights therein.
66
+
67
+ DISCLAIMER OF WARRANTIES
68
+ THOUGHTSPOT DISCLAIMS RESPONSIBILITY FOR ANY HARM RESULTING FROM COMPANY'S USE OF THIS TOOL. THOUGHTSPOT DISCLAIMS TO THE FULLEST EXTENT PERMITTED, ALL GUARANTEES AND EXPRESS, IMPLIED AND STATUTORY WARRANTIES, INCLUDING WITHOUT LIMITATION THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT OF PROPRIETARY RIGHTS, AND ANY WARRANTIES REGARDING THE AVAILABILITY, SECURITY, RELIABILITY, TIMELINESS AND PERFORMANCE OF THIS TOOL.
69
+
70
+ COMPANY DOWNLOADS AND USES THIS TOOL AT ITS OWN DISCRETION AND RISK, AND COMPANY IS SOLELY RESPONSIBLE FOR ANY DAMAGES TO ITS HARDWARE DEVICES OR LOSS OF DATA THAT RESULT FROM THE DOWNLOAD OR USE OF THIS TOOL.
71
+
72
+ INTELLECTUAL PROPERTY
73
+ ThoughtSpot and its licensors own all right, title, and interest in and to this Tool, including all intellectual property or other proprietary rights worldwide therein, including patent, trademark, service mark, copyright, trade secret, know-how, moral right, and any other intellectual and intangible property rights, including all continuations, continuations in part, applications, renewals, and extensions of any of the foregoing, whether registered or unregistered. All rights not expressly granted herein are reserved.
74
+
75
+ INDEMNIFICATION
76
+ Company will indemnify and hold harmless ThoughtSpot from any claim made by any third party due to or arising directly or indirectly out of its conduct or any connection with its use of this Tool, violation of the terms herein, and any violation of any applicable law or regulation.
77
+
78
+ ThoughtSpot reserves the right, at its own expense, to assume the exclusive defense and control of any manner subject to indemnification by Company, but doing so will not excuse Company's indemnity obligations.
79
+
80
+ LIMITATION OF LIABILITY
81
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT WILL THOUGHTSPOT AND ITS LICENSORS BE LIABLE FOR ANY LOST PROFITS OR BUSINESS OPPORTUNITIES, LOSS OF USE, BUSINESS INTERRUPTION, LOSS OF DATA, OR ANY OTHER INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES UNDER ANY THEORY OF LIABILITY, WHETHER BASED IN CONTRACT, TORT, NEGLIGENCE, PRODUCT LIABILITY, OR OTHERWISE.
82
+
83
+ BECAUSE SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE PRECEDING LIMITATION MAY NOT APPLY TO COMPANY.
84
+
85
+ THOUGHTSPOT'S AND ITS LICENSORS' LIABILITY UNDER THIS EULA WILL NOT, IN ANY EVENT, EXCEED $10 USD.
86
+
87
+ THE FOREGOING LIMITATIONS SHALL APPLY REGARDLESS OF WHETHER THOUGHTSPOT OR ITS LICENSORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND REGARDLESS OF WHETHER ANY REMEDY FAILS OF ITS ESSENTIAL PURPOSE.
88
+
89
+ LEGAL COMPLIANCE
90
+ A Tool may be subject to United States export control regulations. Without prior authorization from the United States government, Company shall not use the Tool for, and shall not permit the Tool to be used for, any purposes prohibited by United States law, including, without limitation, for any prohibited development, design, manufacture or production of missiles or nuclear, chemical or biological weapons.
91
+
92
+ Without limiting the foregoing, Company represents and warrants that:
93
+ (a) Company is not, and is not acting on behalf of, any person who is a citizen, national, or resident of, or who is controlled by the government of, Cuba, Iran, North Korea, Sudan, or Syria, the Crimea Region, or any other country to which the United States has prohibited export transactions;
94
+ (b) Company is not located in a country that is subject to a U.S. Government embargo, or that has been designated by the U.S. Government as a "terrorist supporting" country; and
95
+ (c) Company is not, and is not acting on behalf of, any person or entity listed on the U.S. Treasury Department list of Specially Designated Nationals and Blocked Persons, or the U.S. Commerce Department Denied Persons List, Unverified List, or Entity List or any other U.S. Government list of prohibited or restricted parties unless authorized by license or regulation.
96
+
97
+ In addition, Company is responsible for complying with any local Law that may impact Company's right to import, export, or use the Tool.
98
+
99
+ U.S. GOVERNMENT USE
100
+ If a Tool provided under this Agreement is software, then it is commercial computer software developed exclusively at private expense. Unless otherwise set forth in this Agreement, use, duplication, and disclosure by civilian agencies of the U.S. Government will not exceed those minimum rights set forth in FAR 52.227-19(c) or successor regulations.
101
+
102
+ Use, duplication, and disclosure by U.S. Department of Defense agencies is subject solely to the license terms contained in this EULA, as stated in DFARS 227.7202 or successor regulations. U.S. Government rights will apply only to the specific agency and program for which the Application is obtained.
103
+
104
+ SUPPORT
105
+ If Company has a support question regarding a Tool, use ThoughtSpot's standard support process applicable to Company's access to ThoughtSpot Cloud or license to the ThoughtSpot Application to receive assistance.
106
+
107
+ COMPLAINTS
108
+ Company agrees to direct any questions, complaints, or claims, with respect to the Tools to legal@thoughtspot.com.
109
+
110
+ CONTRACTING PARTIES
111
+ The contracting party is ThoughtSpot, Inc., 444 Castro Street, Suite 1000, Mountain View, CA 94041, the United States of America.
112
+
113
+ This EULA is governed by the laws of the State of California, United States of America, unless mandated by other law. The United Nations Convention for the International Sale of Goods shall not apply.
114
+
115
+ ENTIRE AGREEMENT
116
+ This EULA represents the entire agreement between the parties with respect to the Tool, and supersedes any prior or contemporaneous oral or written agreements concerning the subject matter contained herein.
117
+
118
+ INTERPRETATION
119
+ No failure of either party to exercise or enforce any of its rights under this EULA will act as a waiver of those rights. This EULA may only be modified, or any rights under it waived, by a written agreement executed by the party against which it is asserted.
120
+
121
+ If any provision of this EULA is found illegal or unenforceable, it will be enforced to the maximum extent permissible, and the legality and enforceability of the other provisions of this EULA will not be affected.
122
+
123
+ Any translation of this EULA is done for local requirements and in the event of a dispute between the English and any non-English version, the English version of this EULA shall govern.
124
+
125
+ If Company is located in the province of Quebec, Canada, the following clause applies:
126
+ The parties hereby confirm that Company has requested this EULA and all related documents be drafted in English.
127
+ Les parties ont exige que le present contrat et tous les documents connexes soient rediges en anglais.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @thoughtspot/mcp-auth
2
+
3
+ Reusable ThoughtSpot OAuth + bearer authentication for Cloudflare Workers MCP servers.
4
+
5
+ Extracted from [thoughtspot/mcp-server](https://github.com/thoughtspot/mcp-server) so multiple MCP servers (e.g. `mcp-server`, `spotter-code`) can share the same auth flow.
6
+
7
+ ## What it provides
8
+
9
+ - OAuth 2.0 authorize/callback/token/register flow against a ThoughtSpot instance (SAML-based)
10
+ - Bearer/token endpoints (`/bearer/*`, `/token/*`) for direct access-token-based auth
11
+ - A pluggable Hono app that mounts onto any Cloudflare Workers MCP server based on `agents/mcp` + `@modelcontextprotocol/sdk`
12
+ - Approval dialog + token-callback HTML pages (configurable branding)
13
+
14
+ ## Install
15
+
16
+ This package is **not yet published to npm**. Consume it directly from source:
17
+
18
+ ```bash
19
+ # local checkout (recommended during development):
20
+ npm install file:../ts-mcp-auth
21
+
22
+ # or straight from the repo (the `prepare` script builds dist/ on install):
23
+ npm install git+https://github.com/thoughtspot/ts-mcp-auth.git
24
+ ```
25
+
26
+ Peer deps (provide these in the consuming Worker): `@cloudflare/workers-oauth-provider`, `hono`.
27
+
28
+ ## Usage
29
+
30
+ ```ts
31
+ import { createOAuthHandler, PUBLIC_ROUTES } from "@thoughtspot/mcp-auth";
32
+
33
+ const handler = createOAuthHandler({
34
+ serverInfo: {
35
+ name: "My MCP Server",
36
+ logo: "https://example.com/logo.png",
37
+ description: "MCP Server for X",
38
+ },
39
+ mcpServerClass: MyMcpDurableObject,
40
+ routes: { mcp: "/auth/mcp", sse: "/auth/sse" },
41
+ hooks: {
42
+ onAuthMetric: (name, status, ctx, req) => { /* optional */ },
43
+ extendProps: (req, base) => ({ ...base, customField: "..." }),
44
+ },
45
+ });
46
+
47
+ export default {
48
+ fetch: handler.fetch,
49
+ };
50
+ ```
51
+
52
+ ## Static assets
53
+
54
+ The token callback page expects `oauth-callback.{html,css,js}` available via an `assetsFetcher`. Either ship the bundled assets in `./assets/` to your Worker's `[assets]` binding, or pass a custom fetcher.
55
+
56
+ ## License
57
+
58
+ ThoughtSpot Development Tools End User License Agreement — see [LICENSE](LICENSE).
@@ -0,0 +1,101 @@
1
+ body {
2
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
3
+ display: flex;
4
+ justify-content: center;
5
+ align-items: center;
6
+ height: 100vh;
7
+ margin: 0;
8
+ background-color: #f8f9fa;
9
+ color: #2c3e50;
10
+ }
11
+ .container {
12
+ text-align: center;
13
+ padding: 3rem;
14
+ background: white;
15
+ border-radius: 12px;
16
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
17
+ max-width: 480px;
18
+ width: 90%;
19
+ }
20
+ .logo {
21
+ width: 48px;
22
+ height: 48px;
23
+ margin-bottom: 1.5rem;
24
+ }
25
+ h2 {
26
+ font-size: 1.5rem;
27
+ font-weight: 600;
28
+ margin: 0 0 1rem 0;
29
+ color: #1a1a1a;
30
+ }
31
+ .spinner {
32
+ border: 3px solid #e9ecef;
33
+ border-top: 3px solid #0066cc;
34
+ border-radius: 50%;
35
+ width: 36px;
36
+ height: 36px;
37
+ animation: spin 1s linear infinite;
38
+ margin: 1.5rem auto;
39
+ }
40
+ @keyframes spin {
41
+ 0% { transform: rotate(0deg); }
42
+ 100% { transform: rotate(360deg); }
43
+ }
44
+ #status {
45
+ font-size: 0.95rem;
46
+ color: #495057;
47
+ margin: 1rem 0;
48
+ line-height: 1.5;
49
+ }
50
+ .footer {
51
+ margin-top: 2rem;
52
+ font-size: 0.85rem;
53
+ color: #6c757d;
54
+ }
55
+
56
+ .warning-banner {
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: space-between;
60
+ background: #FFF8E1;
61
+ border: 1px solid #FFE082;
62
+ border-radius: 8px;
63
+ box-shadow: 0 2px 8px rgba(16, 30, 54, 0.04);
64
+ padding: 12px 16px;
65
+ margin-bottom: 1rem;
66
+ }
67
+
68
+ .warning-icon {
69
+ flex-shrink: 0;
70
+ margin-right: 10px;
71
+ align-self: flex-start;
72
+ margin-top: 2px;
73
+ }
74
+
75
+ .warning-text {
76
+ flex: 1;
77
+ margin: 0;
78
+ font-size: 14px;
79
+ line-height: 1.4;
80
+ color: #333;
81
+ text-align: left;
82
+ max-width: 100%;
83
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
84
+ }
85
+
86
+ .warning-close {
87
+ background: none;
88
+ border: none;
89
+ font-size: 16px;
90
+ line-height: 1;
91
+ color: #999;
92
+ opacity: 0.7;
93
+ cursor: pointer;
94
+ padding: 0;
95
+ align-self: flex-start;
96
+ margin-top: 2px;
97
+ margin-left: 5px;
98
+ }
99
+ .warning-close:hover {
100
+ opacity: 1;
101
+ }
@@ -0,0 +1,64 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>ThoughtSpot Authorization</title>
5
+ <link rel="stylesheet" href="oauth-callback.css">
6
+ </head>
7
+ <body>
8
+ <div class="container">
9
+ <img src="https://avatars.githubusercontent.com/u/8906680?s=200&v=4" alt="ThoughtSpot Logo" class="logo">
10
+ <h2>Authorization in Progress</h2>
11
+ <div class="spinner"></div>
12
+ <p id="status">Establishing secure connection...</p>
13
+ <div class="footer">
14
+ ThoughtSpot Spotter
15
+ </div>
16
+ </div>
17
+ <!-- Card -->
18
+ <div id="manual-token-section" style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #f4f5f7; z-index: 9999; flex-direction: column; justify-content: center; align-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
19
+ <div style="background: #fff; border-radius: 16px; box-shadow: 0 2px 12px 0 rgba(16,30,54,0.08); padding: 2.5rem 2rem 2rem 2rem; max-width: 440px; width: 100%; margin: 0 auto; display: flex; flex-direction: column; align-items: stretch;">
20
+ <div class="warning-banner">
21
+ <svg class="warning-icon" width="15" height="15" viewBox="0 0 24 24" fill="none">
22
+ <circle cx="12" cy="12" r="12" fill="#FFE082"/>
23
+ <path d="M12 8v4m0 4h.01" stroke="#FF9800" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
24
+ <circle cx="12" cy="12" r="9" stroke="#FF9800" stroke-width="2" fill="none"/>
25
+ </svg>
26
+ <p class="warning-text">
27
+ Browser privacy settings, network issues, or strict cookie settings may be impacting authentication. <br>
28
+ Take the additional steps below to fix the issue.
29
+ </p>
30
+ <button class="warning-close" aria-label="Dismiss">&times;</button>
31
+ </div>
32
+ <div style="font-size: 1.1rem; font-weight: 500; color: #23272f; margin-bottom: 1.1rem; text-align: center; letter-spacing: -0.01em;">ThoughtSpot Spotter wants access<br>to your ThoughtSpot instance</div>
33
+ <div style="font-size: 1rem; color: #23272f; font-weight: 500; margin-bottom: 0.5rem; text-align: left;">Complete the below steps to finish authenticating:</div>
34
+ <ul style="text-align:left; margin: 0 0 1.2rem 1.1rem; padding: 0; color: #444; font-size: 0.9rem; line-height: 1.7; font-weight: 400;">
35
+ <li>Open this <a id="manual-token-url-link" href="#" style="color:#2563eb; text-decoration:underline;">token URL</a> in a new tab</li>
36
+ <li>Copy the token value or JSON</li>
37
+ <li>Paste the token value or JSON into the box below</li>
38
+ </ul>
39
+ <label for="manual-token-input" style="margin-bottom:0.4rem; font-size: 0.8rem; color: #23272f; font-weight: 500; align-self: flex-start;">Token value or JSON</label>
40
+ <textarea id="manual-token-input" rows="6" style="width:100%; max-width:100%; font-family:monospace; font-size:1rem; border: 1.2px solid #e0e3e8; border-radius: 8px; padding: 14px 14px; background: #fafbfc; margin-bottom: 1.4rem; resize: vertical; box-sizing: border-box; outline: none; transition: border 0.2s; min-height: 90px;"></textarea>
41
+ <div style="display: flex; width: 100%; gap: 0.8rem; margin-top: 0.1rem;">
42
+ <button id="manual-back-btn" style="flex:1; padding:12px 0; font-size:1.05rem; background: #f6f7f9; color: #23272f; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; transition: background 0.2s;">Back</button>
43
+ <button id="submit-manual-token" style="flex:1; padding:12px 0; font-size:1.05rem; background: #2563eb; color: #fff; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; transition: background 0.2s;">Submit</button>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ <div id="free-trial-error-section" style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #f4f5f7; z-index: 9999; flex-direction: column; justify-content: center; align-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
48
+ <div style="background: #fff; border-radius: 16px; box-shadow: 0 2px 12px 0 rgba(16,30,54,0.08); padding: 2.5rem 2rem 2rem 2rem; max-width: 440px; width: 100%; margin: 0 auto; display: flex; flex-direction: column; align-items: stretch;">
49
+ <div class="warning-banner">
50
+ <svg class="warning-icon" width="15" height="15" viewBox="0 0 24 24" fill="none">
51
+ <circle cx="12" cy="12" r="12" fill="#FFE082"/>
52
+ <path d="M12 8v4m0 4h.01" stroke="#FF9800" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
53
+ <circle cx="12" cy="12" r="9" stroke="#FF9800" stroke-width="2" fill="none"/>
54
+ </svg>
55
+ <p id="free-trial-error-text" class="warning-text">
56
+
57
+ </p>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ <script type="application/json" id="oauth-req-info">{{OAUTH_REQ_INFO}}</script>
62
+ <script src="oauth-callback.js"></script>
63
+ </body>
64
+ </html>
@@ -0,0 +1,163 @@
1
+ // Immediately invoke the async function
2
+ (async () => {
3
+ const oauthReqInfo = JSON.parse(document.getElementById('oauth-req-info').textContent);
4
+
5
+ // Ensure manual section is hidden initially
6
+ const manualSection = document.getElementById('manual-token-section');
7
+ const freeTrialErrorSection = document.getElementById('free-trial-error-section');
8
+ const container = document.querySelector('.container');
9
+ manualSection.style.display = 'none';
10
+
11
+ try {
12
+ // Check if INSTANCE_URL is available and valid
13
+ if (!window.INSTANCE_URL) {
14
+ throw new Error('Instance URL not available. Please ensure you accessed this page through the proper OAuth flow.');
15
+ }
16
+
17
+ const base = new URL(window.INSTANCE_URL);
18
+ if (!base.pathname.endsWith('/')) {
19
+ base.pathname += '/';
20
+ }
21
+ const tokenUrl = new URL('callosum/v1/v2/auth/token/fetch?validity_time_in_sec=2592000', base.toString());
22
+
23
+ document.getElementById('status').textContent = 'Retrieving authentication token...';
24
+
25
+ const response = await fetch(tokenUrl.toString(), {
26
+ method: 'GET',
27
+ credentials: 'include'
28
+ });
29
+
30
+ if (!response.ok) {
31
+ if (response.status === 401) {
32
+
33
+
34
+ // TODO: Remove this once we have a proper way to handle this
35
+ // This is a temporary fix to handle the case where the instance URL is a free trial instance URL
36
+ // Since, free trial does not support IAMv2, the user needs to login to the free trial instance in a separate tab manually.
37
+ if (base.toString().match(/^https:\/\/(?:team|my)\d+\.thoughtspot\.cloud\/?$/)) {
38
+ freeTrialErrorSection.style.display = 'flex';
39
+ const errorTextEl = document.getElementById('free-trial-error-text');
40
+ errorTextEl.textContent = 'Click ';
41
+ const link = document.createElement('a');
42
+ link.href = base.toString();
43
+ link.target = '_blank';
44
+ link.textContent = 'here';
45
+ errorTextEl.appendChild(link);
46
+ errorTextEl.append(' to login to your ThoughtSpot free trial instance in a separate tab and then refresh this page.');
47
+ return;
48
+ }
49
+
50
+ // 401 likely due to 3rd party cookies being blocked
51
+ manualSection.style.display = 'flex';
52
+ document.getElementById('status').textContent = '';
53
+ container.style.display = 'none';
54
+
55
+ // Set up the token URL link to open in a new tab
56
+ const tokenLink = document.getElementById('manual-token-url-link');
57
+ tokenLink.href = tokenUrl.toString();
58
+ tokenLink.target = '_blank';
59
+
60
+ document.getElementById('manual-back-btn').onclick = () => {
61
+ window.history.back();
62
+ };
63
+ document.querySelector('.warning-close').onclick = () => {
64
+ document.querySelector('.warning-banner').style.display = 'none';
65
+ };
66
+ document.getElementById('submit-manual-token').onclick = async () => {
67
+ const tokenText = document.getElementById('manual-token-input').value;
68
+ let tokenData;
69
+ try {
70
+ // If the text starts with "data", wrap it in curly braces to make it valid JSON
71
+ const jsonText = tokenText.trim().startsWith('"data"') ? `{${tokenText}}` : tokenText;
72
+ const parsed = JSON.parse(jsonText);
73
+
74
+ // Handle different token formats
75
+ if (typeof parsed === 'string') {
76
+ // Case 1: tokenText is a quoted string
77
+ tokenData = { data: { token: parsed } };
78
+ } else if (parsed.data?.token) {
79
+ // Case 2: { data: { token: ... } }
80
+ tokenData = { data: { token: parsed.data.token } };
81
+ } else if (parsed.token) {
82
+ // Case 3: { token: ... }
83
+ tokenData = { data: { token: parsed.token } };
84
+ } else {
85
+ throw new Error('Unrecognized token format.');
86
+ }
87
+ } catch (e) {
88
+ // If JSON parsing fails, try to extract token from the string
89
+ const tokenMatch = tokenText.match(/"token"\s*:\s*"([^"]+)"/);
90
+ if (tokenMatch) {
91
+ tokenData = { data: { token: tokenMatch[1] } };
92
+ } else if (typeof tokenText === 'string' && tokenText.trim().length > 0) {
93
+ // Case 4: raw token string
94
+ tokenData = { data: { token: tokenText.trim() } };
95
+ } else {
96
+ document.getElementById('status').textContent = 'Invalid token format. Please paste the correct token.';
97
+ document.getElementById('status').style.color = '#dc3545';
98
+ return;
99
+ }
100
+ }
101
+ document.getElementById('status').textContent = 'Submitting token...';
102
+ document.getElementById('status').style.color = '#495057';
103
+ try {
104
+ const storeResponse = await fetch('/store-token', {
105
+ method: 'POST',
106
+ headers: {
107
+ 'Content-Type': 'application/json'
108
+ },
109
+ body: JSON.stringify({
110
+ token: tokenData,
111
+ oauthReqInfo: oauthReqInfo,
112
+ instanceUrl: window.INSTANCE_URL
113
+ })
114
+ });
115
+ const responseData = await storeResponse.json();
116
+ if (!storeResponse.ok) {
117
+ const errorText = await storeResponse.text();
118
+ throw new Error(`Failed to store token (Status: ${storeResponse.status}): ${errorText}`);
119
+ }
120
+ window.location.href = responseData.redirectTo;
121
+ } catch (err) {
122
+ document.getElementById('status').textContent = err.message;
123
+ document.getElementById('status').style.color = '#dc3545';
124
+ }
125
+ };
126
+ return;
127
+ }
128
+ const errorText = await response.text();
129
+ throw new Error(`Authentication failed (Status: ${response.status}): ${errorText}`);
130
+ }
131
+
132
+ const data = await response.json();
133
+ document.getElementById('status').textContent = 'Authentication successful. Securing your session...';
134
+
135
+ // Send the token to the server
136
+ const storeResponse = await fetch('/store-token', {
137
+ method: 'POST',
138
+ headers: {
139
+ 'Content-Type': 'application/json'
140
+ },
141
+ body: JSON.stringify({
142
+ token: data,
143
+ oauthReqInfo: oauthReqInfo,
144
+ instanceUrl: window.INSTANCE_URL
145
+ })
146
+ });
147
+ const responseData = await storeResponse.json();
148
+
149
+ if (!storeResponse.ok) {
150
+ const errorText = await storeResponse.text();
151
+ throw new Error(`Failed to store token (Status: ${storeResponse.status}): ${errorText}`);
152
+ }
153
+
154
+ window.location.href = responseData.redirectTo;
155
+
156
+ } catch (error) {
157
+ console.error('Error:', error);
158
+ document.getElementById('status').textContent = error.message;
159
+ document.getElementById('status').style.color = '#dc3545';
160
+ document.querySelector('h2').textContent = 'Authorization Failed';
161
+ document.querySelector('.spinner').style.display = 'none';
162
+ }
163
+ })();
@@ -0,0 +1,34 @@
1
+ import type { Hono } from "hono";
2
+ import type { AuthHooks, BaseProps } from "./types";
3
+ export interface BearerMCPServer {
4
+ serve(path: string, options?: unknown): {
5
+ fetch: (req: any, env: any, ctx: any) => Promise<Response>;
6
+ };
7
+ serveSSE(path: string, options?: unknown): {
8
+ fetch: (req: any, env: any, ctx: any) => Promise<Response>;
9
+ };
10
+ }
11
+ export interface WithBearerHandlerOptions<P extends BaseProps = BaseProps> {
12
+ hooks?: AuthHooks<P>;
13
+ /**
14
+ * Path prefix overrides. By default `/bearer/*` and `/token/*` mount.
15
+ * Override e.g. to `/auth/bearer` + `/auth/token` for multi-mode servers.
16
+ */
17
+ prefixes?: {
18
+ bearer?: string;
19
+ token?: string;
20
+ };
21
+ /**
22
+ * Inner-route paths (relative to the prefix). Default `/mcp` and `/sse`.
23
+ */
24
+ innerRoutes?: {
25
+ mcp?: string;
26
+ sse?: string;
27
+ };
28
+ }
29
+ export declare function withBearerHandler<P extends BaseProps = BaseProps, HonoEnv extends {
30
+ Bindings: any;
31
+ } = {
32
+ Bindings: any;
33
+ }>(app: Hono<HonoEnv>, MCPServer: BearerMCPServer, options?: WithBearerHandlerOptions<P>): Hono<HonoEnv>;
34
+ //# sourceMappingURL=bearer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../src/bearer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAGjC,OAAO,KAAK,EACX,SAAS,EACT,SAAS,EAET,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,eAAe;IAE/B,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG;QAAE,KAAK,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAC;IAEvG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG;QAAE,KAAK,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAC;CAC1G;AAED,MAAM,WAAW,wBAAwB,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS;IACxE,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF;;OAEG;IACH,WAAW,CAAC,EAAE;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;KACb,CAAC;CACF;AAsHD,wBAAgB,iBAAiB,CAChC,CAAC,SAAS,SAAS,GAAG,SAAS,EAE/B,OAAO,SAAS;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,EAErD,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,EAClB,SAAS,EAAE,eAAe,EAC1B,OAAO,GAAE,wBAAwB,CAAC,CAAC,CAAM,GACvC,IAAI,CAAC,OAAO,CAAC,CAiCf"}
package/dist/bearer.js ADDED
@@ -0,0 +1,91 @@
1
+ import { validateAndSanitizeUrl } from "./oauth-utils";
2
+ import { PUBLIC_ROUTES, PUBLIC_ROUTE_PREFIXES } from "./routes";
3
+ function getAuthMetricRouteGroup(pathname, authRouteFamily, ssePath) {
4
+ if (pathname.endsWith(ssePath)) {
5
+ return authRouteFamily === "bearer" ? "bearer_sse" : "token_sse";
6
+ }
7
+ return authRouteFamily === "bearer" ? "bearer_mcp" : "token_mcp";
8
+ }
9
+ function recordBearerMetric(hooks, status, ctx, req, group) {
10
+ if (!hooks?.onBearerMetric)
11
+ return;
12
+ try {
13
+ hooks.onBearerMetric(status, ctx, req, group);
14
+ }
15
+ catch (err) {
16
+ console.error("onBearerMetric hook failed:", err);
17
+ }
18
+ }
19
+ async function handleTokenAuth(req, env, ctx, MCPServer, authRouteFamily, hooks, mcpPath, ssePath) {
20
+ const url = new URL(req.url);
21
+ const authMetricRouteGroup = getAuthMetricRouteGroup(url.pathname, authRouteFamily, ssePath);
22
+ try {
23
+ const authHeader = req.headers.get("authorization");
24
+ if (!authHeader) {
25
+ const response = new Response("Bearer token is required", { status: 400 });
26
+ recordBearerMetric(hooks, response.status, ctx, req, authMetricRouteGroup);
27
+ return response;
28
+ }
29
+ let accessToken = authHeader.split(" ")[1];
30
+ let tsHost;
31
+ if (accessToken?.includes("@")) {
32
+ [accessToken, tsHost] = accessToken.split("@");
33
+ }
34
+ else {
35
+ tsHost = req.headers.get("x-ts-host");
36
+ }
37
+ if (!accessToken) {
38
+ const response = new Response("Bearer token is required", {
39
+ status: 400,
40
+ });
41
+ recordBearerMetric(hooks, response.status, ctx, req, authMetricRouteGroup);
42
+ return response;
43
+ }
44
+ if (!tsHost) {
45
+ const response = new Response("TS Host is required, either in the authorization header as 'token@ts-host' or as a separate 'x-ts-host' header", { status: 400 });
46
+ recordBearerMetric(hooks, response.status, ctx, req, authMetricRouteGroup);
47
+ return response;
48
+ }
49
+ const clientName = req.headers.get("x-ts-client-name") || "Bearer Token client";
50
+ const baseProps = {
51
+ accessToken: accessToken,
52
+ instanceUrl: validateAndSanitizeUrl(tsHost),
53
+ clientName: {
54
+ clientId: clientName,
55
+ clientName,
56
+ registrationDate: Date.now(),
57
+ },
58
+ };
59
+ const props = hooks?.extendProps
60
+ ? hooks.extendProps(req, baseProps)
61
+ : baseProps;
62
+ ctx.props = props;
63
+ let response;
64
+ const pathname = url.pathname;
65
+ if (pathname.endsWith(mcpPath)) {
66
+ response = await MCPServer.serve(mcpPath).fetch(req, env, ctx);
67
+ }
68
+ else if (pathname.endsWith(ssePath)) {
69
+ response = await MCPServer.serveSSE(ssePath).fetch(req, env, ctx);
70
+ }
71
+ else {
72
+ response = new Response("Not found", { status: 404 });
73
+ }
74
+ recordBearerMetric(hooks, response.status, ctx, req, authMetricRouteGroup);
75
+ return response;
76
+ }
77
+ catch (error) {
78
+ recordBearerMetric(hooks, 500, ctx, req, authMetricRouteGroup);
79
+ throw error;
80
+ }
81
+ }
82
+ export function withBearerHandler(app, MCPServer, options = {}) {
83
+ const bearerPrefix = options.prefixes?.bearer ?? PUBLIC_ROUTE_PREFIXES.bearer;
84
+ const tokenPrefix = options.prefixes?.token ?? PUBLIC_ROUTE_PREFIXES.token;
85
+ const mcpPath = options.innerRoutes?.mcp ?? PUBLIC_ROUTES.mcp;
86
+ const ssePath = options.innerRoutes?.sse ?? PUBLIC_ROUTES.sse;
87
+ app.mount(bearerPrefix, (req, env, ctx) => handleTokenAuth(req, env, ctx, MCPServer, "bearer", options.hooks, mcpPath, ssePath));
88
+ app.mount(tokenPrefix, (req, env, ctx) => handleTokenAuth(req, env, ctx, MCPServer, "token", options.hooks, mcpPath, ssePath));
89
+ return app;
90
+ }
91
+ //# sourceMappingURL=bearer.js.map