121 lines
3.7 KiB
JavaScript
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,
|
|
};
|