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;
}
}