164 lines
4.3 KiB
JavaScript
164 lines
4.3 KiB
JavaScript
import crypto from 'crypto';
|
|
import {
|
|
getApiBaseUrl,
|
|
getAuthorizeBaseUrl,
|
|
getScopes,
|
|
getScopesString,
|
|
getTokenExpiryDate,
|
|
isAccessTokenExpired,
|
|
getRequiredAuthConfig,
|
|
getBasicAuthHeader,
|
|
logger,
|
|
} from './shared.js';
|
|
|
|
const TOKEN_PATH = '/identity/v1/oauth2/token';
|
|
|
|
async function mintToken(marketplace, body) {
|
|
const response = await fetch(`${getApiBaseUrl(marketplace)}${TOKEN_PATH}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
Authorization: getBasicAuthHeader(marketplace),
|
|
},
|
|
body: new URLSearchParams(body).toString(),
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (!response.ok || data.error) {
|
|
const message = data.error_description || data.error || response.statusText;
|
|
logger.error(`eBay token request failed: ${message}`);
|
|
throw new Error(`eBay token request failed: ${message}`);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
export function createAuthorizationUrl(marketplace, { state } = {}) {
|
|
const { clientId } = getRequiredAuthConfig(marketplace);
|
|
const { ruName, locale, prompt } = marketplace.config || {};
|
|
|
|
if (!ruName) {
|
|
throw new Error('eBay marketplace is missing required config (ruName)');
|
|
}
|
|
|
|
const url = new URL('/oauth2/authorize', getAuthorizeBaseUrl(marketplace));
|
|
url.searchParams.set('client_id', clientId);
|
|
url.searchParams.set('redirect_uri', ruName);
|
|
url.searchParams.set('response_type', 'code');
|
|
url.searchParams.set('scope', getScopesString(marketplace));
|
|
|
|
if (state) {
|
|
url.searchParams.set('state', state);
|
|
}
|
|
if (locale) {
|
|
url.searchParams.set('locale', locale);
|
|
}
|
|
if (prompt) {
|
|
url.searchParams.set('prompt', prompt);
|
|
}
|
|
|
|
return url.toString();
|
|
}
|
|
|
|
export async function exchangeAuthorizationCode(marketplace, { code }) {
|
|
const { ruName } = marketplace.config || {};
|
|
if (!code) {
|
|
throw new Error('Missing eBay authorization code');
|
|
}
|
|
if (!ruName) {
|
|
throw new Error('eBay marketplace is missing required config (ruName)');
|
|
}
|
|
|
|
const tokenData = await mintToken(marketplace, {
|
|
grant_type: 'authorization_code',
|
|
code,
|
|
redirect_uri: ruName,
|
|
});
|
|
|
|
return {
|
|
configUpdates: {
|
|
accessToken: tokenData.access_token,
|
|
accessTokenExpiresAt: getTokenExpiryDate(tokenData.expires_in).toISOString(),
|
|
refreshToken: tokenData.refresh_token || marketplace.config.refreshToken,
|
|
scopes: getScopes(marketplace),
|
|
tokenType: tokenData.token_type,
|
|
},
|
|
marketplaceUpdates: {
|
|
connected: true,
|
|
connectedAt: new Date(),
|
|
},
|
|
data: {
|
|
expiresIn: tokenData.expires_in,
|
|
tokenType: tokenData.token_type,
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function refreshAuth(marketplace) {
|
|
const { refreshToken } = marketplace.config || {};
|
|
if (!refreshToken) {
|
|
throw new Error('eBay marketplace is missing required config (refreshToken)');
|
|
}
|
|
|
|
const tokenData = await mintToken(marketplace, {
|
|
grant_type: 'refresh_token',
|
|
refresh_token: refreshToken,
|
|
scope: getScopesString(marketplace),
|
|
});
|
|
|
|
return {
|
|
configUpdates: {
|
|
accessToken: tokenData.access_token,
|
|
accessTokenExpiresAt: getTokenExpiryDate(tokenData.expires_in).toISOString(),
|
|
refreshToken: tokenData.refresh_token || refreshToken,
|
|
scopes: getScopes(marketplace),
|
|
tokenType: tokenData.token_type,
|
|
lastTokenRefreshAt: new Date().toISOString(),
|
|
},
|
|
data: {
|
|
expiresIn: tokenData.expires_in,
|
|
tokenType: tokenData.token_type,
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function ensureAuthenticatedMarketplace(marketplace) {
|
|
if (!isAccessTokenExpired(marketplace)) {
|
|
return { marketplace };
|
|
}
|
|
|
|
const authResult = await refreshAuth(marketplace);
|
|
return {
|
|
marketplace: {
|
|
...marketplace,
|
|
config: {
|
|
...(marketplace.config || {}),
|
|
...authResult.configUpdates,
|
|
},
|
|
},
|
|
configUpdates: authResult.configUpdates,
|
|
};
|
|
}
|
|
|
|
export function canVerifyWebhookSignature(marketplace) {
|
|
return !!marketplace.config?.verificationToken;
|
|
}
|
|
|
|
export function verifyWebhookSignature(marketplace, rawBody, signature) {
|
|
const verificationToken = marketplace.config?.verificationToken;
|
|
if (!verificationToken) {
|
|
return false;
|
|
}
|
|
|
|
const hash = crypto
|
|
.createHash('sha256')
|
|
.update(rawBody + verificationToken)
|
|
.digest('base64');
|
|
|
|
try {
|
|
return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(signature));
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|