|
1 | 1 | import {fetchApiVersions} from '@shopify/cli-kit/node/api/admin' |
2 | 2 | import {AbortError} from '@shopify/cli-kit/node/error' |
3 | | -import {fetch} from '@shopify/cli-kit/node/http' |
4 | 3 | import {outputContent, outputDebug, outputToken} from '@shopify/cli-kit/node/output' |
5 | 4 | 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' |
15 | 9 |
|
16 | 10 | export interface AdminStoreGraphQLContext { |
17 | 11 | adminSession: AdminSession |
18 | 12 | version: string |
19 | 13 | sessionUserId: string |
20 | 14 | } |
21 | 15 |
|
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 | | - |
114 | 16 | async function resolveApiVersion(options: { |
115 | 17 | session: StoredStoreAppSession |
116 | 18 | adminSession: AdminSession |
|
0 commit comments