← back to Shopify Oauth Handler
server.js
79 lines
#!/usr/bin/env node
/**
* One-shot Shopify OAuth callback handler for the FA 5-11-26 Partner app.
* Listens on :9895, receives the install redirect, exchanges code for an
* admin API access token, writes it to ./token.txt, then exits.
*
* Run: CLIENT_ID=... CLIENT_SECRET=... node server.js
*/
const http = require('http');
const fs = require('fs');
const { URL } = require('url');
const PORT = 9895;
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;
const EXPECT_SHOP = 'designer-laboratory-sandbox.myshopify.com';
if (!CLIENT_ID || !CLIENT_SECRET) {
console.error('FATAL: CLIENT_ID and CLIENT_SECRET env vars required');
process.exit(1);
}
const server = http.createServer(async (req, res) => {
const u = new URL(req.url, `http://localhost:${PORT}`);
if (u.pathname !== '/oauth/callback') {
res.writeHead(404).end('not found');
return;
}
const code = u.searchParams.get('code');
const shop = u.searchParams.get('shop');
console.log(`[oauth] callback hit: shop=${shop} code=${code ? code.slice(0,8)+'...' : '(none)'}`);
if (!code || !shop) {
res.writeHead(400).end('missing code or shop param');
return;
}
if (shop !== EXPECT_SHOP) {
res.writeHead(400).end(`unexpected shop: ${shop}`);
return;
}
try {
const r = await fetch(`https://${shop}/admin/oauth/access_token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code
})
});
const j = await r.json();
if (!j.access_token) {
console.error('[oauth] no access_token in response:', JSON.stringify(j));
res.writeHead(500).end('token exchange failed: ' + JSON.stringify(j));
return;
}
fs.writeFileSync(__dirname + '/token.txt', j.access_token + '\n', { mode: 0o600 });
fs.writeFileSync(__dirname + '/scopes.txt', (j.scope || '') + '\n', { mode: 0o600 });
console.log(`[oauth] SUCCESS — token written (prefix ${j.access_token.slice(0,14)}..., scopes=${j.scope})`);
res.writeHead(200, { 'Content-Type': 'text/html' }).end(
`<html><body style="font:18px system-ui;padding:40px;background:#0d1117;color:#7ee787">
<h1>✓ Token received</h1>
<p>Scopes: <code>${j.scope}</code></p>
<p>Token written to <code>token.txt</code> (last 6: <code>...${j.access_token.slice(-6)}</code>).</p>
<p>You can close this tab. Claude will pick it up.</p>
</body></html>`
);
setTimeout(() => process.exit(0), 500);
} catch (e) {
console.error('[oauth] exchange error:', e);
res.writeHead(500).end('exchange error: ' + e.message);
}
});
server.listen(PORT, '127.0.0.1', () => {
console.log(`[oauth] listening on http://localhost:${PORT}/oauth/callback`);
console.log(`[oauth] expected shop: ${EXPECT_SHOP}`);
console.log(`[oauth] client_id: ${CLIENT_ID.slice(0,8)}...`);
});