ken_nogi/PleasanterSystem/dbbackup_XXX/lib/lineworksUserAuth.js
Kenichiro NOGI ed33892f08 chore: 作業中の変更を整理しコミット(複数プロジェクト分)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 11:09:50 +09:00

121 lines
3.7 KiB
JavaScript

"use strict";
/*
* LINE WORKS User Account認証 (OAuth 2.0 認可コードフロー)。
* Drive/共有ドライブAPIはService Account認証(JWT)では利用できないため、
* backup.js のドライブ操作はこちらの実ユーザー認証を使う。
*
* 初回セットアップ: `node lineworks-authorize.js` を一度だけ実行し、
* ブラウザでの認可 → refresh_token取得 → .envへの保存 を行う。
* 以降 backup.js は getUserAccessToken() で refresh_token から
* access_token を都度再発行する(ユーザー操作不要)。
*
* 仕様書: https://developers.worksmobile.com/jp/docs/auth-oauth
*/
const { updateEnvValue } = require("./env");
const LW_AUTHORIZE_URL = "https://auth.worksmobile.com/oauth2/v2.0/authorize";
const LW_TOKEN_URL = "https://auth.worksmobile.com/oauth2/v2.0/token";
function requiredEnv(key) {
const value = process.env[key];
if (!value) {
throw new Error(`環境変数が不足しています: ${key}`);
}
return value;
}
function buildAuthorizeUrl({ state }) {
const clientId = requiredEnv("LW_CLIENT_ID");
const redirectUri = requiredEnv("LW_REDIRECT_URI");
const scope = requiredEnv("LW_SCOPE_DRIVE");
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
response_type: "code",
scope,
state,
});
return `${LW_AUTHORIZE_URL}?${params.toString()}`;
}
async function requestToken(form) {
const response = await fetch(LW_TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: form,
});
const raw = await response.text();
let data;
try {
data = raw ? JSON.parse(raw) : {};
} catch (_err) {
throw new Error(`トークンエンドポイントのレスポンスがJSONではありません: ${raw}`);
}
if (!response.ok) {
throw new Error(`トークン取得失敗: ${response.status} ${raw}`);
}
return data;
}
async function exchangeCodeForToken(code) {
const clientId = requiredEnv("LW_CLIENT_ID");
const clientSecret = requiredEnv("LW_CLIENT_SECRET");
const redirectUri = requiredEnv("LW_REDIRECT_URI");
return requestToken(
new URLSearchParams({
code,
grant_type: "authorization_code",
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
})
);
}
// refresh_token から access_token を再発行する。Refresh Token Rotation が ON のため、
// レスポンスに含まれる新しい refresh_token を必ず .env へ書き戻してから access_token を返す。
async function getUserAccessToken() {
const clientId = requiredEnv("LW_CLIENT_ID");
const clientSecret = requiredEnv("LW_CLIENT_SECRET");
const refreshToken = process.env.LW_USER_REFRESH_TOKEN;
if (!refreshToken) {
throw new Error(
"LW_USER_REFRESH_TOKEN が未設定です。先に 'node lineworks-authorize.js' を実行してください。"
);
}
const data = await requestToken(
new URLSearchParams({
refresh_token: refreshToken,
grant_type: "refresh_token",
client_id: clientId,
client_secret: clientSecret,
})
);
if (!data.access_token) {
throw new Error("アクセストークン再発行失敗: access_token が返却されませんでした");
}
if (data.refresh_token) {
updateEnvValue("LW_USER_REFRESH_TOKEN", data.refresh_token);
}
return data.access_token;
}
module.exports = {
buildAuthorizeUrl,
exchangeCodeForToken,
getUserAccessToken,
LW_AUTHORIZE_URL,
LW_TOKEN_URL,
};