Skip to content

Commit 3892b28

Browse files
committed
Make store auth scopes additive
1 parent 3cc7aff commit 3892b28

4 files changed

Lines changed: 564 additions & 115 deletions

File tree

packages/cli/src/cli/services/store/admin-graphql-context.ts

Lines changed: 4 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,18 @@
11
import {fetchApiVersions} from '@shopify/cli-kit/node/api/admin'
22
import {AbortError} from '@shopify/cli-kit/node/error'
3-
import {fetch} from '@shopify/cli-kit/node/http'
43
import {outputContent, outputDebug, outputToken} from '@shopify/cli-kit/node/output'
54
import {AdminSession} from '@shopify/cli-kit/node/session'
6-
import {maskToken, STORE_AUTH_APP_CLIENT_ID} from './auth-config.js'
7-
import {createStoredStoreAuthError, reauthenticateStoreAuthError} from './auth-recovery.js'
8-
import {
9-
clearStoredStoreAppSession,
10-
getStoredStoreAppSession,
11-
isSessionExpired,
12-
setStoredStoreAppSession,
13-
StoredStoreAppSession,
14-
} from './session.js'
5+
import {reauthenticateStoreAuthError} from './auth-recovery.js'
6+
import {clearStoredStoreAppSession} from './session.js'
7+
import type {StoredStoreAppSession} from './session.js'
8+
import {loadStoredStoreSession} from './stored-session.js'
159

1610
export interface AdminStoreGraphQLContext {
1711
adminSession: AdminSession
1812
version: string
1913
sessionUserId: string
2014
}
2115

22-
async function refreshStoreToken(session: StoredStoreAppSession): Promise<StoredStoreAppSession> {
23-
if (!session.refreshToken) {
24-
throw reauthenticateStoreAuthError(`No refresh token stored for ${session.store}.`, session.store, session.scopes.join(','))
25-
}
26-
27-
const endpoint = `https://${session.store}/admin/oauth/access_token`
28-
29-
outputDebug(
30-
outputContent`Refreshing expired token for ${outputToken.raw(session.store)} (expired at ${outputToken.raw(session.expiresAt ?? 'unknown')}, refresh_token=${outputToken.raw(maskToken(session.refreshToken))})`,
31-
)
32-
33-
const response = await fetch(endpoint, {
34-
method: 'POST',
35-
headers: {'Content-Type': 'application/json'},
36-
body: JSON.stringify({
37-
client_id: STORE_AUTH_APP_CLIENT_ID,
38-
grant_type: 'refresh_token',
39-
refresh_token: session.refreshToken,
40-
}),
41-
})
42-
43-
const body = await response.text()
44-
45-
if (!response.ok) {
46-
outputDebug(
47-
outputContent`Token refresh failed with HTTP ${outputToken.raw(String(response.status))}: ${outputToken.raw(body.slice(0, 300))}`,
48-
)
49-
clearStoredStoreAppSession(session.store, session.userId)
50-
throw reauthenticateStoreAuthError(
51-
`Token refresh failed for ${session.store} (HTTP ${response.status}).`,
52-
session.store,
53-
session.scopes.join(','),
54-
)
55-
}
56-
57-
let data: {access_token?: string; refresh_token?: string; expires_in?: number; refresh_token_expires_in?: number}
58-
try {
59-
data = JSON.parse(body)
60-
} catch {
61-
clearStoredStoreAppSession(session.store, session.userId)
62-
throw new AbortError('Received an invalid refresh response from Shopify.')
63-
}
64-
65-
if (!data.access_token) {
66-
clearStoredStoreAppSession(session.store, session.userId)
67-
throw reauthenticateStoreAuthError(
68-
`Token refresh returned an invalid response for ${session.store}.`,
69-
session.store,
70-
session.scopes.join(','),
71-
)
72-
}
73-
74-
const now = Date.now()
75-
const expiresAt = data.expires_in ? new Date(now + data.expires_in * 1000).toISOString() : session.expiresAt
76-
77-
const refreshedSession: StoredStoreAppSession = {
78-
...session,
79-
accessToken: data.access_token,
80-
refreshToken: data.refresh_token ?? session.refreshToken,
81-
expiresAt,
82-
refreshTokenExpiresAt: data.refresh_token_expires_in
83-
? new Date(now + data.refresh_token_expires_in * 1000).toISOString()
84-
: session.refreshTokenExpiresAt,
85-
acquiredAt: new Date(now).toISOString(),
86-
}
87-
88-
outputDebug(
89-
outputContent`Token refresh succeeded for ${outputToken.raw(session.store)}: ${outputToken.raw(maskToken(session.accessToken))}${outputToken.raw(maskToken(refreshedSession.accessToken))}, new expiry ${outputToken.raw(expiresAt ?? 'unknown')}`,
90-
)
91-
92-
setStoredStoreAppSession(refreshedSession)
93-
return refreshedSession
94-
}
95-
96-
async function loadStoredStoreSession(store: string): Promise<StoredStoreAppSession> {
97-
let session = getStoredStoreAppSession(store)
98-
99-
if (!session) {
100-
throw createStoredStoreAuthError(store)
101-
}
102-
103-
outputDebug(
104-
outputContent`Loaded stored session for ${outputToken.raw(store)}: token=${outputToken.raw(maskToken(session.accessToken))}, expires=${outputToken.raw(session.expiresAt ?? 'unknown')}`,
105-
)
106-
107-
if (isSessionExpired(session)) {
108-
session = await refreshStoreToken(session)
109-
}
110-
111-
return session
112-
}
113-
11416
async function resolveApiVersion(options: {
11517
session: StoredStoreAppSession
11618
adminSession: AdminSession

0 commit comments

Comments
 (0)