ken_nogi/lineworks-sync/get-token.js
Kenichiro NOGI 88a402ce0f up
2026-07-10 18:13:30 +09:00

101 lines
3.4 KiB
JavaScript

"use strict";
/*
* アクセストークンだけを標準出力するヘルパー
* lineworks-anythingllm.js の認証ロジックを流用
*
* 使い方:
* node get-token.js
* → アクセストークン文字列だけが標準出力される(それ以外は何も出力しない)
*/
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const LW_TOKEN_URL = "https://auth.worksmobile.com/oauth2/v2.0/token";
const PRIVATE_KEY_FILE = process.env.LW_PRIVATE_KEY_FILE
? path.resolve(process.env.LW_PRIVATE_KEY_FILE)
: path.join(__dirname, "private_20260307184804.key");
const LW_CLIENT_ID = process.env.LW_CLIENT_ID || "tre8J_Tk8RblfsMSyZRh";
const LW_CLIENT_SECRET = process.env.LW_CLIENT_SECRET || "O5_R5gcHxg";
const LW_SERVICE_ACCOUNT = process.env.LW_SERVICE_ACCOUNT || "wh4k8.serviceaccount@next-hd.co.jp";
const LW_SCOPE = process.env.LW_SCOPE || "board board.read";
const LW_PRIVATE_KEY = (process.env.LW_PRIVATE_KEY && process.env.LW_PRIVATE_KEY.trim())
? process.env.LW_PRIVATE_KEY
: (fs.existsSync(PRIVATE_KEY_FILE) ? fs.readFileSync(PRIVATE_KEY_FILE, "utf8") : "");
function base64UrlEncode(value) {
return Buffer.from(value)
.toString("base64")
.replace(/=/g, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
}
function createJwtAssertion() {
const now = Math.floor(Date.now() / 1000);
const header = { alg: "RS256", typ: "JWT" };
const payload = {
iss: LW_CLIENT_ID,
sub: LW_SERVICE_ACCOUNT,
aud: LW_TOKEN_URL,
iat: now,
exp: now + 300,
};
const encodedHeader = base64UrlEncode(JSON.stringify(header));
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
const signingInput = `${encodedHeader}.${encodedPayload}`;
const signer = crypto.createSign("RSA-SHA256");
signer.update(signingInput);
signer.end();
const signature = signer
.sign(LW_PRIVATE_KEY)
.toString("base64")
.replace(/=/g, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
return `${signingInput}.${signature}`;
}
// デバッグ用: 実際に使われている値をstderrに出力(トークン取得の可否には影響しない)
process.stderr.write(`[DEBUG] LW_CLIENT_ID=${LW_CLIENT_ID}\n`);
process.stderr.write(`[DEBUG] LW_SERVICE_ACCOUNT=${LW_SERVICE_ACCOUNT}\n`);
process.stderr.write(`[DEBUG] LW_PRIVATE_KEY_FILE=${PRIVATE_KEY_FILE}\n`);
process.stderr.write(`[DEBUG] 秘密鍵ファイル存在=${fs.existsSync(PRIVATE_KEY_FILE)}\n`);
process.stderr.write(`[DEBUG] LW_SCOPE=${LW_SCOPE}\n`);
async function main() {
const assertion = createJwtAssertion();
const form = new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion,
client_id: LW_CLIENT_ID,
client_secret: LW_CLIENT_SECRET,
scope: LW_SCOPE,
});
const response = await fetch(LW_TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: form,
});
if (!response.ok) {
process.stderr.write(`アクセストークン取得失敗: ${response.status} ${await response.text()}\n`);
process.exitCode = 1;
return;
}
const data = await response.json();
// 標準出力にはトークンのみを出す(PowerShell側で変数に取り込みやすくするため)
process.stdout.write(data.access_token);
}
main();