const fs = require('fs'); const path = require('path'); const jwt = require('jsonwebtoken'); let cachedToken = null; let cachedExpiry = 0; let cachedScopeKey = ''; const CLIENT_ID = 'V58hNobcAqRcQPerySMz'; const CLIENT_SECRET = 'gJ84XQtigD'; const SERVICE_ACCOUNT = 'x94xh.serviceaccount@works-nextgroup2'; const TOKEN_ENDPOINT = 'https://auth.worksmobile.com/oauth2/v2.0/token'; const PRIVATE_KEY_PATH = path.join(__dirname, 'private_20250530180606.key'); const TOKEN_TTL_BUFFER = 30; // seconds const DEFAULT_SCOPES = ['bot', 'contact']; function readPrivateKey() { return fs.readFileSync(PRIVATE_KEY_PATH, 'utf8'); } function makeScopeKey(scopes) { return scopes.slice().sort().join(' '); } function isTokenValid(scopeKey) { return Boolean(cachedToken) && cachedScopeKey === scopeKey && cachedExpiry > Math.floor(Date.now() / 1000) + TOKEN_TTL_BUFFER; } async function getAccessToken(scopes = DEFAULT_SCOPES) { const normalizedScopes = Array.isArray(scopes) && scopes.length > 0 ? scopes : DEFAULT_SCOPES; const scopeKey = makeScopeKey(normalizedScopes); if (isTokenValid(scopeKey)) { return cachedToken; } const now = Math.floor(Date.now() / 1000); const payload = { iss: CLIENT_ID, sub: SERVICE_ACCOUNT, iat: now, exp: now + 5 * 60, aud: TOKEN_ENDPOINT }; const assertion = jwt.sign(payload, readPrivateKey(), { algorithm: 'RS256' }); const body = new URLSearchParams({ assertion, grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', client_id: CLIENT_ID, client_secret: CLIENT_SECRET, scope: normalizedScopes.join(' ') }); const response = await fetch(TOKEN_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString() }); if (!response.ok) { const errorText = await response.text(); throw new Error(`LINE WORKSアクセストークン取得失敗: ${errorText}`); } const data = await response.json(); cachedToken = data.access_token; cachedExpiry = now + (data.expires_in || 300); cachedScopeKey = scopeKey; return cachedToken; } module.exports = { getAccessToken };