@helloleo/plugins 0.2.20 → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +51 -4
- package/package.json +1 -1
- package/src/tools/setup-netsuite.ts +51 -4
package/dist/index.js
CHANGED
|
@@ -25613,7 +25613,9 @@ function netsuiteServerTs() {
|
|
|
25613
25613
|
* Config is read from the Cloudflare env binding via @helloleo/runtime.
|
|
25614
25614
|
* Set NETSUITE_ACCOUNT_ID / _CLIENT_ID / _CLIENT_SECRET (secret optional for a
|
|
25615
25615
|
* PKCE public client) and optional PUBLIC_APP_URL as HelloLeo Cloud secrets
|
|
25616
|
-
* (prod) or dev vars (preview).
|
|
25616
|
+
* (prod) or dev vars (preview). NETSUITE_BROKER_CALLBACK (written by the
|
|
25617
|
+
* HelloLeo wizard) switches the redirect URI to the shared OAuth broker so
|
|
25618
|
+
* one URI serves every app/environment. They never reach the browser.
|
|
25617
25619
|
*/
|
|
25618
25620
|
import { eq } from 'drizzle-orm'
|
|
25619
25621
|
import { env as runtimeEnv } from '@helloleo/runtime'
|
|
@@ -25624,6 +25626,7 @@ interface NsEnv {
|
|
|
25624
25626
|
NETSUITE_ACCOUNT_ID?: string
|
|
25625
25627
|
NETSUITE_CLIENT_ID?: string
|
|
25626
25628
|
NETSUITE_CLIENT_SECRET?: string
|
|
25629
|
+
NETSUITE_BROKER_CALLBACK?: string
|
|
25627
25630
|
PUBLIC_APP_URL?: string
|
|
25628
25631
|
HELLOLEO_URL?: string
|
|
25629
25632
|
}
|
|
@@ -25684,8 +25687,47 @@ export function appOrigin(request: Request) {
|
|
|
25684
25687
|
url.host
|
|
25685
25688
|
return \`\${proto}://\${hostHeader}\`
|
|
25686
25689
|
}
|
|
25690
|
+
/**
|
|
25691
|
+
* HelloLeo's shared OAuth broker (integration-proxy Worker). When
|
|
25692
|
+
* NETSUITE_BROKER_CALLBACK is set (the platform writes it to the project env),
|
|
25693
|
+
* customers register that ONE stable URI on their Integration Record instead
|
|
25694
|
+
* of one URI per app/environment: the authorize redirect goes to the broker,
|
|
25695
|
+
* the broker 302s the callback back to THIS app (looked up by the state
|
|
25696
|
+
* nonce), and the token exchange passes the same broker URI. Unset (self-host
|
|
25697
|
+
* / standalone), the app falls back to its own origin's callback.
|
|
25698
|
+
*/
|
|
25699
|
+
export function brokerCallback(): string | null {
|
|
25700
|
+
const raw = nsEnv.NETSUITE_BROKER_CALLBACK
|
|
25701
|
+
if (!raw) return null
|
|
25702
|
+
try {
|
|
25703
|
+
const u = new URL(raw)
|
|
25704
|
+
return u.protocol === 'https:' ? u.toString() : null
|
|
25705
|
+
} catch {
|
|
25706
|
+
return null
|
|
25707
|
+
}
|
|
25708
|
+
}
|
|
25709
|
+
|
|
25687
25710
|
export function redirectUri(request: Request) {
|
|
25688
|
-
return \`\${appOrigin(request)}/auth/netsuite/callback\`
|
|
25711
|
+
return brokerCallback() ?? \`\${appOrigin(request)}/auth/netsuite/callback\`
|
|
25712
|
+
}
|
|
25713
|
+
|
|
25714
|
+
/**
|
|
25715
|
+
* Tell the broker where to send this flow's callback: nonce -> this app's
|
|
25716
|
+
* origin. MUST succeed before redirecting to NetSuite, otherwise the callback
|
|
25717
|
+
* dead-ends at the broker. No-op when the broker isn't configured.
|
|
25718
|
+
*/
|
|
25719
|
+
export async function registerWithBroker(request: Request, flowId: string): Promise<void> {
|
|
25720
|
+
const callback = brokerCallback()
|
|
25721
|
+
if (!callback) return
|
|
25722
|
+
const registerUrl = new URL('/auth/netsuite/register', callback)
|
|
25723
|
+
const res = await fetch(registerUrl.toString(), {
|
|
25724
|
+
method: 'POST',
|
|
25725
|
+
headers: { 'Content-Type': 'application/json' },
|
|
25726
|
+
body: JSON.stringify({ nonce: flowId, target: appOrigin(request) }),
|
|
25727
|
+
})
|
|
25728
|
+
if (!res.ok) {
|
|
25729
|
+
throw new Error(\`NetSuite broker registration failed (HTTP \${res.status}) -- cannot start sign-in\`)
|
|
25730
|
+
}
|
|
25689
25731
|
}
|
|
25690
25732
|
|
|
25691
25733
|
// Coerce ?return_to= to our OWN origin. The callback appends the session bearer
|
|
@@ -25849,6 +25891,7 @@ import {
|
|
|
25849
25891
|
authorizeUrl,
|
|
25850
25892
|
pkce,
|
|
25851
25893
|
redirectUri,
|
|
25894
|
+
registerWithBroker,
|
|
25852
25895
|
requireConfig,
|
|
25853
25896
|
safeReturnTo,
|
|
25854
25897
|
SCOPE,
|
|
@@ -25866,6 +25909,10 @@ export const Route = createFileRoute('/auth/netsuite/start')({
|
|
|
25866
25909
|
|
|
25867
25910
|
const { verifier, challenge } = await pkce()
|
|
25868
25911
|
const flowId = crypto.randomUUID()
|
|
25912
|
+
// With the shared broker, NetSuite redirects to the broker's fixed
|
|
25913
|
+
// callback and the broker forwards here -- it must know this flow's
|
|
25914
|
+
// target before the user leaves for NetSuite.
|
|
25915
|
+
await registerWithBroker(request, flowId)
|
|
25869
25916
|
await db.insert(netsuiteFlows).values({ flowId, codeVerifier: verifier, returnTo })
|
|
25870
25917
|
|
|
25871
25918
|
const authorize = new URL(authorizeUrl(accountId))
|
|
@@ -26728,8 +26775,8 @@ import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
|
|
26728
26775
|
"CONFIG -- NETSUITE_ACCOUNT_ID, NETSUITE_CLIENT_ID, NETSUITE_CLIENT_SECRET (secret optional for PKCE public clients). Never hardcode; never put in code. Same var names in every environment, different SOURCE:",
|
|
26729
26776
|
"- DEV (Fly preview): read from the project .env. HelloLeo writes them there (the setup wizard, or the env tooling). In dev the app uses .env -- do NOT tell the user to use HelloLeo Cloud here.",
|
|
26730
26777
|
"- PROD (published Worker): the SAME names live as HelloLeo Cloud secrets; the publish step promotes them from .env onto the Worker automatically. Same code path (@helloleo/runtime env), just a different source per environment.",
|
|
26731
|
-
"- PUBLIC_APP_URL / HELLOLEO_URL are set by the platform per environment; don't hardcode them.",
|
|
26732
|
-
"- REDIRECT URI -- never guess or hardcode it.
|
|
26778
|
+
"- PUBLIC_APP_URL / HELLOLEO_URL / NETSUITE_BROKER_CALLBACK are set by the platform per environment; don't hardcode them.",
|
|
26779
|
+
"- REDIRECT URI -- never guess or hardcode it. When NETSUITE_BROKER_CALLBACK is set (the HelloLeo wizard writes it), the redirect URI is HelloLeo's shared OAuth broker: ONE stable URI the user registers ONCE on Integration Record #2 (scope: rest_webservices), valid for both the preview and the published app -- the broker routes each callback back to this app. Without it, the app falls back to its own origin's callback. Either way, read the exact value from GET /auth/netsuite/config (or getNetSuiteRedirectUri() on the client) and tell the user to register THAT.",
|
|
26733
26780
|
"- Never replace <AuthLayout> with a password gate. Real OAuth is the only supported login."
|
|
26734
26781
|
].join(`
|
|
26735
26782
|
`);
|
package/package.json
CHANGED
|
@@ -107,7 +107,9 @@ function netsuiteServerTs(): string {
|
|
|
107
107
|
* Config is read from the Cloudflare env binding via @helloleo/runtime.
|
|
108
108
|
* Set NETSUITE_ACCOUNT_ID / _CLIENT_ID / _CLIENT_SECRET (secret optional for a
|
|
109
109
|
* PKCE public client) and optional PUBLIC_APP_URL as HelloLeo Cloud secrets
|
|
110
|
-
* (prod) or dev vars (preview).
|
|
110
|
+
* (prod) or dev vars (preview). NETSUITE_BROKER_CALLBACK (written by the
|
|
111
|
+
* HelloLeo wizard) switches the redirect URI to the shared OAuth broker so
|
|
112
|
+
* one URI serves every app/environment. They never reach the browser.
|
|
111
113
|
*/
|
|
112
114
|
import { eq } from 'drizzle-orm'
|
|
113
115
|
import { env as runtimeEnv } from '@helloleo/runtime'
|
|
@@ -118,6 +120,7 @@ interface NsEnv {
|
|
|
118
120
|
NETSUITE_ACCOUNT_ID?: string
|
|
119
121
|
NETSUITE_CLIENT_ID?: string
|
|
120
122
|
NETSUITE_CLIENT_SECRET?: string
|
|
123
|
+
NETSUITE_BROKER_CALLBACK?: string
|
|
121
124
|
PUBLIC_APP_URL?: string
|
|
122
125
|
HELLOLEO_URL?: string
|
|
123
126
|
}
|
|
@@ -178,8 +181,47 @@ export function appOrigin(request: Request) {
|
|
|
178
181
|
url.host
|
|
179
182
|
return \`\${proto}://\${hostHeader}\`
|
|
180
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* HelloLeo's shared OAuth broker (integration-proxy Worker). When
|
|
186
|
+
* NETSUITE_BROKER_CALLBACK is set (the platform writes it to the project env),
|
|
187
|
+
* customers register that ONE stable URI on their Integration Record instead
|
|
188
|
+
* of one URI per app/environment: the authorize redirect goes to the broker,
|
|
189
|
+
* the broker 302s the callback back to THIS app (looked up by the state
|
|
190
|
+
* nonce), and the token exchange passes the same broker URI. Unset (self-host
|
|
191
|
+
* / standalone), the app falls back to its own origin's callback.
|
|
192
|
+
*/
|
|
193
|
+
export function brokerCallback(): string | null {
|
|
194
|
+
const raw = nsEnv.NETSUITE_BROKER_CALLBACK
|
|
195
|
+
if (!raw) return null
|
|
196
|
+
try {
|
|
197
|
+
const u = new URL(raw)
|
|
198
|
+
return u.protocol === 'https:' ? u.toString() : null
|
|
199
|
+
} catch {
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
181
204
|
export function redirectUri(request: Request) {
|
|
182
|
-
return \`\${appOrigin(request)}/auth/netsuite/callback\`
|
|
205
|
+
return brokerCallback() ?? \`\${appOrigin(request)}/auth/netsuite/callback\`
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Tell the broker where to send this flow's callback: nonce -> this app's
|
|
210
|
+
* origin. MUST succeed before redirecting to NetSuite, otherwise the callback
|
|
211
|
+
* dead-ends at the broker. No-op when the broker isn't configured.
|
|
212
|
+
*/
|
|
213
|
+
export async function registerWithBroker(request: Request, flowId: string): Promise<void> {
|
|
214
|
+
const callback = brokerCallback()
|
|
215
|
+
if (!callback) return
|
|
216
|
+
const registerUrl = new URL('/auth/netsuite/register', callback)
|
|
217
|
+
const res = await fetch(registerUrl.toString(), {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
headers: { 'Content-Type': 'application/json' },
|
|
220
|
+
body: JSON.stringify({ nonce: flowId, target: appOrigin(request) }),
|
|
221
|
+
})
|
|
222
|
+
if (!res.ok) {
|
|
223
|
+
throw new Error(\`NetSuite broker registration failed (HTTP \${res.status}) -- cannot start sign-in\`)
|
|
224
|
+
}
|
|
183
225
|
}
|
|
184
226
|
|
|
185
227
|
// Coerce ?return_to= to our OWN origin. The callback appends the session bearer
|
|
@@ -345,6 +387,7 @@ import {
|
|
|
345
387
|
authorizeUrl,
|
|
346
388
|
pkce,
|
|
347
389
|
redirectUri,
|
|
390
|
+
registerWithBroker,
|
|
348
391
|
requireConfig,
|
|
349
392
|
safeReturnTo,
|
|
350
393
|
SCOPE,
|
|
@@ -362,6 +405,10 @@ export const Route = createFileRoute('/auth/netsuite/start')({
|
|
|
362
405
|
|
|
363
406
|
const { verifier, challenge } = await pkce()
|
|
364
407
|
const flowId = crypto.randomUUID()
|
|
408
|
+
// With the shared broker, NetSuite redirects to the broker's fixed
|
|
409
|
+
// callback and the broker forwards here -- it must know this flow's
|
|
410
|
+
// target before the user leaves for NetSuite.
|
|
411
|
+
await registerWithBroker(request, flowId)
|
|
365
412
|
await db.insert(netsuiteFlows).values({ flowId, codeVerifier: verifier, returnTo })
|
|
366
413
|
|
|
367
414
|
const authorize = new URL(authorizeUrl(accountId))
|
|
@@ -1260,8 +1307,8 @@ async function executeStart(dir: string): Promise<string> {
|
|
|
1260
1307
|
"CONFIG -- NETSUITE_ACCOUNT_ID, NETSUITE_CLIENT_ID, NETSUITE_CLIENT_SECRET (secret optional for PKCE public clients). Never hardcode; never put in code. Same var names in every environment, different SOURCE:",
|
|
1261
1308
|
"- DEV (Fly preview): read from the project .env. HelloLeo writes them there (the setup wizard, or the env tooling). In dev the app uses .env -- do NOT tell the user to use HelloLeo Cloud here.",
|
|
1262
1309
|
"- PROD (published Worker): the SAME names live as HelloLeo Cloud secrets; the publish step promotes them from .env onto the Worker automatically. Same code path (@helloleo/runtime env), just a different source per environment.",
|
|
1263
|
-
"- PUBLIC_APP_URL / HELLOLEO_URL are set by the platform per environment; don't hardcode them.",
|
|
1264
|
-
"- REDIRECT URI -- never guess or hardcode it.
|
|
1310
|
+
"- PUBLIC_APP_URL / HELLOLEO_URL / NETSUITE_BROKER_CALLBACK are set by the platform per environment; don't hardcode them.",
|
|
1311
|
+
"- REDIRECT URI -- never guess or hardcode it. When NETSUITE_BROKER_CALLBACK is set (the HelloLeo wizard writes it), the redirect URI is HelloLeo's shared OAuth broker: ONE stable URI the user registers ONCE on Integration Record #2 (scope: rest_webservices), valid for both the preview and the published app -- the broker routes each callback back to this app. Without it, the app falls back to its own origin's callback. Either way, read the exact value from GET /auth/netsuite/config (or getNetSuiteRedirectUri() on the client) and tell the user to register THAT.",
|
|
1265
1312
|
"- Never replace <AuthLayout> with a password gate. Real OAuth is the only supported login.",
|
|
1266
1313
|
].join("\n");
|
|
1267
1314
|
}
|