@sanity/sdk 3.3.0-rc.0 → 3.3.0-rc.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/dist/_exports/_internal.d.ts +3 -3
- package/dist/_exports/_internal.js +1 -1
- package/dist/_exports/agent.d.ts +1 -1
- package/dist/_exports/comlink.d.ts +1 -1
- package/dist/_exports/dashboard.d.ts +1 -1
- package/dist/{authStore-BGoEGxOq.d.ts → authStore-7dZCfKUq.d.ts} +2 -2
- package/dist/{authStore-BGoEGxOq.d.ts.map → authStore-7dZCfKUq.d.ts.map} +1 -1
- package/dist/{createSanityInstance-BQIqyGLX.d.ts → createSanityInstance--Lrldto8.d.ts} +6 -3
- package/dist/createSanityInstance--Lrldto8.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +27 -20
- package/dist/index.js.map +1 -1
- package/dist/{topicStore-DXvuH9h4.d.ts → topicStore-CAnJsiqI.d.ts} +2 -2
- package/dist/{topicStore-DXvuH9h4.d.ts.map → topicStore-CAnJsiqI.d.ts.map} +1 -1
- package/dist/{version-CIhX5ppJ.js → version-CYs30C7v.js} +2 -2
- package/dist/{version-CIhX5ppJ.js.map → version-CYs30C7v.js.map} +1 -1
- package/package.json +1 -1
- package/src/auth/oauth/oauthActions.test.ts +42 -1
- package/src/auth/oauth/oauthActions.ts +33 -17
- package/src/config/authConfig.ts +5 -2
- package/dist/createSanityInstance-BQIqyGLX.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -43,6 +43,11 @@ const oauthConfig = {
|
|
|
43
43
|
organizationId: 'org123',
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
const oauthConfigNoOrg = {
|
|
47
|
+
clientId: 'client-abc',
|
|
48
|
+
redirectUri: 'https://app.example.com/callback',
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
const seededTokens: OAuthTokens = {
|
|
47
52
|
accessToken: 'stored-access',
|
|
48
53
|
tokenType: 'bearer',
|
|
@@ -94,6 +99,7 @@ interface SetupOptions {
|
|
|
94
99
|
sessionSeed?: Record<string, string>
|
|
95
100
|
initialLocationHref?: string
|
|
96
101
|
withOAuthConfig?: boolean
|
|
102
|
+
oauthConfig?: {clientId: string; redirectUri: string; organizationId?: string}
|
|
97
103
|
apiHost?: string
|
|
98
104
|
}
|
|
99
105
|
|
|
@@ -112,7 +118,7 @@ function setup(options: SetupOptions = {}) {
|
|
|
112
118
|
storageArea,
|
|
113
119
|
initialLocationHref: options.initialLocationHref ?? 'https://app.example.com/',
|
|
114
120
|
...(options.apiHost && {apiHost: options.apiHost}),
|
|
115
|
-
...(options.withOAuthConfig === false ? {} : {oauth: oauthConfig}),
|
|
121
|
+
...(options.withOAuthConfig === false ? {} : {oauth: options.oauthConfig ?? oauthConfig}),
|
|
116
122
|
},
|
|
117
123
|
})
|
|
118
124
|
|
|
@@ -179,6 +185,17 @@ describe('startOAuthAuthorization', () => {
|
|
|
179
185
|
expect(session.getItem(OAUTH_RETURN_TO_KEY)).toBe('https://app.example.com/callback?x=1')
|
|
180
186
|
})
|
|
181
187
|
|
|
188
|
+
it('omits the resource param when no organizationId is configured', async () => {
|
|
189
|
+
const assign = vi.fn()
|
|
190
|
+
vi.stubGlobal('window', {location: {assign}})
|
|
191
|
+
setup({initialLocationHref: 'https://app.example.com/', oauthConfig: oauthConfigNoOrg})
|
|
192
|
+
|
|
193
|
+
await startOAuthAuthorization(instance!)
|
|
194
|
+
|
|
195
|
+
const url = new URL(assign.mock.calls[0][0])
|
|
196
|
+
expect(url.searchParams.has('resource')).toBe(false)
|
|
197
|
+
})
|
|
198
|
+
|
|
182
199
|
it('throws when OAuth is not configured', async () => {
|
|
183
200
|
setup({withOAuthConfig: false})
|
|
184
201
|
await expect(startOAuthAuthorization(instance!)).rejects.toThrow(/OAuth is not configured/)
|
|
@@ -405,6 +422,18 @@ describe('handleOAuthCallback', () => {
|
|
|
405
422
|
expect(request).not.toHaveBeenCalled()
|
|
406
423
|
})
|
|
407
424
|
|
|
425
|
+
it('omits the resource param when no organizationId is configured', async () => {
|
|
426
|
+
const {request} = setup({
|
|
427
|
+
oauthConfig: oauthConfigNoOrg,
|
|
428
|
+
sessionSeed: {[OAUTH_STATE_KEY]: 'state-xyz', [OAUTH_VERIFIER_KEY]: 'verifier-1'},
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
await handleOAuthCallback(instance!, callbackHref)
|
|
432
|
+
|
|
433
|
+
const body = parseBody(request.mock.calls[0][0].body)
|
|
434
|
+
expect(body.has('resource')).toBe(false)
|
|
435
|
+
})
|
|
436
|
+
|
|
408
437
|
it('sets ERROR when the token exchange fails', async () => {
|
|
409
438
|
const request = vi.fn().mockRejectedValue(new Error('boom'))
|
|
410
439
|
const {session} = setup({
|
|
@@ -439,6 +468,18 @@ describe('refreshOAuthTokens', () => {
|
|
|
439
468
|
expect(readStored(storageArea)).toMatchObject({accessToken: 'new-access'})
|
|
440
469
|
})
|
|
441
470
|
|
|
471
|
+
it('omits the resource param when no organizationId is configured', async () => {
|
|
472
|
+
const {request} = setup({
|
|
473
|
+
oauthConfig: oauthConfigNoOrg,
|
|
474
|
+
storageSeed: {[OAUTH_TOKENS_KEY]: serializeTokens(seededTokens)},
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
await refreshOAuthTokens(instance!)
|
|
478
|
+
|
|
479
|
+
const body = parseBody(request.mock.calls[0][0].body)
|
|
480
|
+
expect(body.has('resource')).toBe(false)
|
|
481
|
+
})
|
|
482
|
+
|
|
442
483
|
it('shares a single in-flight request across concurrent callers', async () => {
|
|
443
484
|
const pending = deferred<typeof tokenResponse>()
|
|
444
485
|
const request = vi.fn().mockReturnValue(pending.promise)
|
|
@@ -48,6 +48,19 @@ function getResourceIndicator(organizationId: string): string {
|
|
|
48
48
|
return `urn:io.sanity:organization:${organizationId}`
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Appends the RFC 8707 `resource` indicator to `params` when an organisation
|
|
53
|
+
* id is configured. Without one, the request is left unscoped.
|
|
54
|
+
*/
|
|
55
|
+
function appendResourceIndicator(
|
|
56
|
+
params: URLSearchParams,
|
|
57
|
+
oauth: NonNullable<AuthOptions['oauth']>,
|
|
58
|
+
): void {
|
|
59
|
+
if (oauth.organizationId) {
|
|
60
|
+
params.append('resource', getResourceIndicator(oauth.organizationId))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
/**
|
|
52
65
|
* Serialises tokens for storage, converting `expiresAt` to an ISO string.
|
|
53
66
|
*
|
|
@@ -89,6 +102,21 @@ function createOAuthClient(options: AuthOptions): SanityClient {
|
|
|
89
102
|
})
|
|
90
103
|
}
|
|
91
104
|
|
|
105
|
+
/** POSTs form-encoded params to the OAuth token endpoint. */
|
|
106
|
+
function postTokenRequest(
|
|
107
|
+
client: SanityClient,
|
|
108
|
+
params: URLSearchParams,
|
|
109
|
+
tag: string,
|
|
110
|
+
): Promise<TokenEndpointResponse> {
|
|
111
|
+
return client.request<TokenEndpointResponse>({
|
|
112
|
+
method: 'POST',
|
|
113
|
+
url: '/auth/oauth/token',
|
|
114
|
+
headers: {'content-type': 'application/x-www-form-urlencoded'},
|
|
115
|
+
body: params.toString(),
|
|
116
|
+
tag,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
92
120
|
/** Converts a token endpoint response to the {@link OAuthTokens} shape. */
|
|
93
121
|
function toOAuthTokens(response: TokenEndpointResponse): OAuthTokens {
|
|
94
122
|
return {
|
|
@@ -140,7 +168,7 @@ export const startOAuthAuthorization = bindActionGlobally(authStore, async ({sta
|
|
|
140
168
|
authorizeUrl.searchParams.set('state', oauthState)
|
|
141
169
|
authorizeUrl.searchParams.set('code_challenge', codeChallenge)
|
|
142
170
|
authorizeUrl.searchParams.set('code_challenge_method', 'S256')
|
|
143
|
-
authorizeUrl.searchParams
|
|
171
|
+
appendResourceIndicator(authorizeUrl.searchParams, options.oauth)
|
|
144
172
|
|
|
145
173
|
logger.info('Starting OAuth authorization')
|
|
146
174
|
if (typeof window !== 'undefined' && typeof window.location?.assign === 'function') {
|
|
@@ -240,15 +268,9 @@ export const handleOAuthCallback = bindActionGlobally(
|
|
|
240
268
|
code_verifier: codeVerifier,
|
|
241
269
|
redirect_uri: options.oauth.redirectUri,
|
|
242
270
|
client_id: options.oauth.clientId,
|
|
243
|
-
resource: getResourceIndicator(options.oauth.organizationId),
|
|
244
|
-
})
|
|
245
|
-
const response = await client.request<TokenEndpointResponse>({
|
|
246
|
-
method: 'POST',
|
|
247
|
-
url: '/auth/oauth/token',
|
|
248
|
-
headers: {'content-type': 'application/x-www-form-urlencoded'},
|
|
249
|
-
body: params.toString(),
|
|
250
|
-
tag: 'oauth.token',
|
|
251
271
|
})
|
|
272
|
+
appendResourceIndicator(params, options.oauth)
|
|
273
|
+
const response = await postTokenRequest(client, params, 'oauth.token')
|
|
252
274
|
|
|
253
275
|
const tokens = toOAuthTokens(response)
|
|
254
276
|
options.storageArea?.setItem(options.storageKey, serializeTokens(tokens))
|
|
@@ -329,15 +351,9 @@ async function doRefreshOAuthTokens({
|
|
|
329
351
|
grant_type: 'refresh_token',
|
|
330
352
|
refresh_token: current.refreshToken,
|
|
331
353
|
client_id: options.oauth.clientId,
|
|
332
|
-
resource: getResourceIndicator(options.oauth.organizationId),
|
|
333
|
-
})
|
|
334
|
-
const response = await client.request<TokenEndpointResponse>({
|
|
335
|
-
method: 'POST',
|
|
336
|
-
url: '/auth/oauth/token',
|
|
337
|
-
headers: {'content-type': 'application/x-www-form-urlencoded'},
|
|
338
|
-
body: params.toString(),
|
|
339
|
-
tag: 'oauth.refresh',
|
|
340
354
|
})
|
|
355
|
+
appendResourceIndicator(params, options.oauth)
|
|
356
|
+
const response = await postTokenRequest(client, params, 'oauth.refresh')
|
|
341
357
|
|
|
342
358
|
const tokens = toOAuthTokens(response)
|
|
343
359
|
if (!tokens.refreshToken) tokens.refreshToken = current.refreshToken
|
package/src/config/authConfig.ts
CHANGED
|
@@ -90,8 +90,11 @@ export interface AuthConfig {
|
|
|
90
90
|
redirectUri: string
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
|
-
* Organisation id used for RFC 8707 resource scoping.
|
|
93
|
+
* Organisation id used for RFC 8707 resource scoping. Optional: when
|
|
94
|
+
* omitted, the `resource` parameter is left off the authorize and token
|
|
95
|
+
* requests, so the issued tokens are not restricted to a single
|
|
96
|
+
* organisation.
|
|
94
97
|
*/
|
|
95
|
-
organizationId
|
|
98
|
+
organizationId?: string
|
|
96
99
|
}
|
|
97
100
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createSanityInstance-BQIqyGLX.d.ts","names":[],"sources":["../src/config/authConfig.ts","../src/config/sanityConfig.ts","../src/store/createSanityInstance.ts"],"mappings":";;;;;UAMiB;;;;EAIf;;;;EAKA;;;;EAKA;;;;EAKA;;;;;;;UAQe;;;;;EAKf;;;;;EAMA,iBAAiB,QAAQ,iBAAiB;;;;;;EAO1C,YAAY,mBAAmB,MAAM,mBAAmB,iBAAiB,QAAQ;;;;;EAMjF;;;;;EAMA,cAAc;;;;;;EAOd;;;;;;EAOA;EAEA;;;;IAIE;;;;;IAMA;;;;IAKA;;;;;;;;;;;UClFa;;EAEf,UAAU;IAAW,OAAO;;IAAkC;;;;;;;;;;UAU/C;;;;;;EAMf;;EAEA;;;;;;;;IAQE,QAAQ;;;;;;;UAQK,cAAc;EAC7B,YAAY;;;;;KAMF;EACV;EACA,uBAAuB;;;;;UAMR;EACf,cAAc,oBAAoB;;;;;UAMnB,cAAc,kCAAkC,4CACvD,cAAc,aAAa;EACnC,UAAU;;;;;;;EAOV,WAAW;;;;;;;;UASI,mBACf,uCACA,kCACA,4CACQ,cAAc,UAAU;EAChC;EACA,cAAc;;;;;;EAMd;;;;;;;;UASe,eACf,uCACA,kCACA,4CACQ,mBAAmB,eAAe,UAAU;EACpD;;;;;;;;;;UAWe,sBAAsB;EACrC;;;;;;UAOe,qBAAqB,eAAe;;;;EAInD,OAAO;;;;;;;;;EASP,SAAS;;;;;;;;KASC,mBAAmB,kBAAkB,uBAAuB;;;;KAK5D;EAAmB;EAAmB;;;;;KAKtC;EAAwB;;;;;KAKxB;EAAkB;;;;;iBAKd,kBAAkB,UAAU,mBAAmB,YAAY;;;;iBAO3D,uBACd,UAAU,mBACT,YAAY;;;;iBAOC,iBAAiB,UAAU,mBAAmB,YAAY;;;;;;UCtLzD;;;;;WAKN;;;;WAKA,QAAQ;;;;;EAMjB;;;;;EAMA;;;;;;EAOA,UAAU;;;;;;;;;iBAUI,qBAAqB,SAAQ,eAAoB"}
|