addashboard-cli 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/commands/ads.js +139 -0
- package/dist/commands/ads.js.map +1 -0
- package/dist/commands/auth.js +75 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/authDevice.js +110 -0
- package/dist/commands/authDevice.js.map +1 -0
- package/dist/commands/authGoogle.js +196 -0
- package/dist/commands/authGoogle.js.map +1 -0
- package/dist/commands/freee.js +249 -0
- package/dist/commands/freee.js.map +1 -0
- package/dist/commands/submission.js +299 -0
- package/dist/commands/submission.js.map +1 -0
- package/dist/index.js +290 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/apiClient.js +86 -0
- package/dist/lib/apiClient.js.map +1 -0
- package/dist/lib/credentialStore.js +76 -0
- package/dist/lib/credentialStore.js.map +1 -0
- package/package.json +54 -0
- package/skills/ad-ops/README.md +139 -0
- package/skills/ad-ops/SKILL.md +146 -0
- package/skills/ad-ops/config.json +5 -0
- package/skills/invoice-issue-freee/README.md +109 -0
- package/skills/invoice-issue-freee/SKILL.md +190 -0
- package/skills/invoice-issue-freee/config.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# addashboard-cli
|
|
2
|
+
|
|
3
|
+
広告ダッシュボード操作用の CLI。詳細な使い方・運用フローは社内マニュアル参照。
|
|
4
|
+
|
|
5
|
+
## インストール
|
|
6
|
+
|
|
7
|
+
Node.js 18+ が必要。
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 都度実行 (推奨)
|
|
11
|
+
npx -y addashboard-cli@latest --help
|
|
12
|
+
|
|
13
|
+
# あるいはグローバルインストール
|
|
14
|
+
npm install -g addashboard-cli
|
|
15
|
+
webgram --help
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## ログイン
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
webgram auth login # ブラウザで認証
|
|
22
|
+
webgram auth whoami # 状態確認
|
|
23
|
+
webgram auth logout
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 主なコマンド
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
webgram list --platform <p> --resource <accounts|campaigns|resources> [--account-id <id>]
|
|
30
|
+
webgram submit --platform <p> --json-file <path> # dry-run 既定
|
|
31
|
+
webgram submit --platform <p> --json-file <path> --no-dry-run --yes # 実入稿
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`<p>` = 対応媒体 (`yda` / `meta` / `google` / `smartnews` / `bytedance`)。
|
|
35
|
+
|
|
36
|
+
入稿ボディ JSON の雛形と必須フィールドは [`fixtures/README.md`](./fixtures/README.md)。
|
|
37
|
+
|
|
38
|
+
## Skill として使う
|
|
39
|
+
|
|
40
|
+
Claude Code / Codex CLI 用の `ad-ops` skill (広告運用スキル) を同梱。配置手順は [`skills/ad-ops/README.md`](./skills/ad-ops/README.md)。
|
|
41
|
+
|
|
42
|
+
## 開発
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install
|
|
46
|
+
npm run build
|
|
47
|
+
npm test
|
|
48
|
+
```
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ALLOWED_RESOURCES = exports.ALLOWED_PLATFORMS = void 0;
|
|
4
|
+
exports.resolvePath = resolvePath;
|
|
5
|
+
exports.runList = runList;
|
|
6
|
+
const apiClient_1 = require("../lib/apiClient");
|
|
7
|
+
exports.ALLOWED_PLATFORMS = ['meta', 'yda', 'google', 'smartnews', 'bytedance'];
|
|
8
|
+
exports.ALLOWED_RESOURCES = ['accounts', 'campaigns', 'resources'];
|
|
9
|
+
/**
|
|
10
|
+
* v5 §5.2: platform × resource → backend エンドポイント解決テーブル。
|
|
11
|
+
*
|
|
12
|
+
* 対応: meta (T-01), yda (T-02), google (T-03), smartnews (T-04), bytedance (T-05)。
|
|
13
|
+
*
|
|
14
|
+
* yda + campaigns に --campaign-id を指定した場合は /yda/campaign-import に振り分ける
|
|
15
|
+
* (Codex PR #41 #2: --campaign-id flag が無視されていた問題の解消)。
|
|
16
|
+
* SmartNews は creative リソースが ad に埋込まれる仕様のため、resources は未対応。
|
|
17
|
+
* bytedance は T-05 で accounts / campaigns / resources GET を追加 (accounts は
|
|
18
|
+
* ApiConnection.selectedAccounts 由来、campaigns / resources は TikTok Marketing API v1.3 実叩き)。
|
|
19
|
+
*/
|
|
20
|
+
function resolvePath(input) {
|
|
21
|
+
const { platform, resource, accountId, campaignId } = input;
|
|
22
|
+
if (!exports.ALLOWED_PLATFORMS.includes(platform)) {
|
|
23
|
+
throw new Error(`unknown platform: ${platform} (allowed: ${exports.ALLOWED_PLATFORMS.join(', ')})`);
|
|
24
|
+
}
|
|
25
|
+
if (!exports.ALLOWED_RESOURCES.includes(resource)) {
|
|
26
|
+
throw new Error(`unknown resource: ${resource} (allowed: ${exports.ALLOWED_RESOURCES.join(', ')})`);
|
|
27
|
+
}
|
|
28
|
+
if (platform === 'meta') {
|
|
29
|
+
if (resource === 'accounts')
|
|
30
|
+
return { path: '/api/submission/meta/accounts', query: {} };
|
|
31
|
+
if (resource === 'campaigns') {
|
|
32
|
+
if (!accountId)
|
|
33
|
+
throw new Error('--account-id が必要です (meta + campaigns)');
|
|
34
|
+
return { path: '/api/submission/meta/campaigns', query: { accountId } };
|
|
35
|
+
}
|
|
36
|
+
if (resource === 'resources') {
|
|
37
|
+
if (!accountId)
|
|
38
|
+
throw new Error('--account-id が必要です (meta + resources)');
|
|
39
|
+
return { path: '/api/submission/meta/resources', query: { accountId } };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (platform === 'yda') {
|
|
43
|
+
if (resource === 'accounts')
|
|
44
|
+
return { path: '/api/submission/yda/accounts', query: {} };
|
|
45
|
+
if (resource === 'campaigns') {
|
|
46
|
+
if (!accountId)
|
|
47
|
+
throw new Error('--account-id が必要です (yda + campaigns)');
|
|
48
|
+
if (campaignId) {
|
|
49
|
+
return { path: '/api/submission/yda/campaign-import', query: { accountId, campaignId } };
|
|
50
|
+
}
|
|
51
|
+
return { path: '/api/submission/yda/campaigns', query: { accountId } };
|
|
52
|
+
}
|
|
53
|
+
if (resource === 'resources') {
|
|
54
|
+
if (!accountId)
|
|
55
|
+
throw new Error('--account-id が必要です (yda + resources)');
|
|
56
|
+
return { path: '/api/submission/yda/resources', query: { accountId } };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (platform === 'google') {
|
|
60
|
+
if (resource === 'accounts')
|
|
61
|
+
return { path: '/api/submission/google/accounts', query: {} };
|
|
62
|
+
if (resource === 'campaigns') {
|
|
63
|
+
if (!accountId)
|
|
64
|
+
throw new Error('--account-id が必要です (google + campaigns)');
|
|
65
|
+
return { path: '/api/submission/google/campaigns', query: { accountId } };
|
|
66
|
+
}
|
|
67
|
+
if (resource === 'resources') {
|
|
68
|
+
if (!accountId)
|
|
69
|
+
throw new Error('--account-id が必要です (google + resources)');
|
|
70
|
+
return { path: '/api/submission/google/resources', query: { accountId } };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (platform === 'smartnews') {
|
|
74
|
+
if (resource === 'accounts')
|
|
75
|
+
return { path: '/api/submission/smartnews/accounts', query: {} };
|
|
76
|
+
if (resource === 'campaigns') {
|
|
77
|
+
if (!accountId)
|
|
78
|
+
throw new Error('--account-id が必要です (smartnews + campaigns)');
|
|
79
|
+
return { path: '/api/submission/smartnews/campaigns', query: { accountId } };
|
|
80
|
+
}
|
|
81
|
+
if (resource === 'resources') {
|
|
82
|
+
throw new Error('smartnews + resources は未対応です (SmartNews v3 は creative を ad に埋込むため独立リソースなし)');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (platform === 'bytedance') {
|
|
86
|
+
if (resource === 'accounts')
|
|
87
|
+
return { path: '/api/submission/bytedance/accounts', query: {} };
|
|
88
|
+
if (resource === 'campaigns') {
|
|
89
|
+
if (!accountId)
|
|
90
|
+
throw new Error('--account-id が必要です (bytedance + campaigns)');
|
|
91
|
+
return { path: '/api/submission/bytedance/campaigns', query: { accountId } };
|
|
92
|
+
}
|
|
93
|
+
if (resource === 'resources') {
|
|
94
|
+
if (!accountId)
|
|
95
|
+
throw new Error('--account-id が必要です (bytedance + resources)');
|
|
96
|
+
return { path: '/api/submission/bytedance/resources', query: { accountId } };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`unsupported: ${platform} + ${resource}`);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* webgram list の本体動作。
|
|
103
|
+
* 副作用は HTTP GET のみ(標準出力には触らない=CLI ラッパー側で整形)。
|
|
104
|
+
*/
|
|
105
|
+
async function runList(input) {
|
|
106
|
+
let resolved;
|
|
107
|
+
try {
|
|
108
|
+
resolved = resolvePath({
|
|
109
|
+
platform: input.platform,
|
|
110
|
+
resource: input.resource,
|
|
111
|
+
accountId: input.accountId,
|
|
112
|
+
campaignId: input.campaignId,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
return { ok: false, error: e.message };
|
|
117
|
+
}
|
|
118
|
+
let client;
|
|
119
|
+
try {
|
|
120
|
+
client = (input.clientFactory ?? apiClient_1.buildAuthedApiClient)(input.store);
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
return { ok: false, error: e.message };
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const res = await client.get(resolved.path, { params: resolved.query });
|
|
127
|
+
return { ok: true, data: res.data };
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const status = err?.response?.status;
|
|
131
|
+
if (status === 401)
|
|
132
|
+
return { ok: false, error: 'unauthorized' };
|
|
133
|
+
const msg = err?.response?.data?.error
|
|
134
|
+
?? err.message
|
|
135
|
+
?? 'network_error';
|
|
136
|
+
return { ok: false, error: msg };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=ads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ads.js","sourceRoot":"","sources":["../../src/commands/ads.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runLogin = runLogin;
|
|
7
|
+
exports.runWhoami = runWhoami;
|
|
8
|
+
exports.runLogout = runLogout;
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
/**
|
|
11
|
+
* webgram auth login のコア動作。
|
|
12
|
+
* 副作用は credentialStore.save のみ(標準出力には触らない=CLI ラッパー側で整形)。
|
|
13
|
+
*/
|
|
14
|
+
async function runLogin(input) {
|
|
15
|
+
try {
|
|
16
|
+
const res = await axios_1.default.post(`${input.endpoint}/api/auth/cli/token`, { username: input.username, password: input.password }, { timeout: 15000, headers: { 'Content-Type': 'application/json' } });
|
|
17
|
+
const { access_token, refresh_token, expires_in } = res.data ?? {};
|
|
18
|
+
if (!access_token || !refresh_token) {
|
|
19
|
+
return { ok: false, error: 'invalid_response' };
|
|
20
|
+
}
|
|
21
|
+
const expiresAt = new Date(Date.now() + (expires_in ?? 3600) * 1000).toISOString();
|
|
22
|
+
const creds = {
|
|
23
|
+
endpoint: input.endpoint,
|
|
24
|
+
accessToken: access_token,
|
|
25
|
+
refreshToken: refresh_token,
|
|
26
|
+
expiresAt,
|
|
27
|
+
};
|
|
28
|
+
input.store.save(creds);
|
|
29
|
+
return { ok: true, email: input.username };
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const code = err?.response?.data?.error ?? 'network_error';
|
|
33
|
+
return { ok: false, error: code };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* webgram auth whoami のコア動作。
|
|
38
|
+
* credentials.json から access_token を取り出して /api/auth/me を叩く。
|
|
39
|
+
*/
|
|
40
|
+
async function runWhoami(input) {
|
|
41
|
+
const creds = input.store.load();
|
|
42
|
+
if (!creds)
|
|
43
|
+
return { ok: false, error: 'not_logged_in' };
|
|
44
|
+
try {
|
|
45
|
+
const res = await axios_1.default.get(`${creds.endpoint}/api/auth/me`, {
|
|
46
|
+
timeout: 15000,
|
|
47
|
+
headers: { Authorization: `Bearer ${creds.accessToken}` },
|
|
48
|
+
});
|
|
49
|
+
return { ok: true, user: res.data };
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const status = err?.response?.status;
|
|
53
|
+
if (status === 401)
|
|
54
|
+
return { ok: false, error: 'unauthorized' };
|
|
55
|
+
return { ok: false, error: 'network_error' };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* webgram auth logout のコア動作。
|
|
60
|
+
* サーバー側 revoke を best-effort で叩き、ローカルの credentials.json は確実に削除する。
|
|
61
|
+
*/
|
|
62
|
+
async function runLogout(input) {
|
|
63
|
+
const creds = input.store.load();
|
|
64
|
+
if (!creds)
|
|
65
|
+
return { ok: true }; // 未ログインでも idempotent
|
|
66
|
+
try {
|
|
67
|
+
await axios_1.default.post(`${creds.endpoint}/api/auth/cli/revoke`, { refresh_token: creds.refreshToken }, { timeout: 15000, headers: { 'Content-Type': 'application/json' } });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// revoke 失敗してもローカルは消す(ユーザー視点の logout は確定させる)
|
|
71
|
+
}
|
|
72
|
+
input.store.clear();
|
|
73
|
+
return { ok: true };
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runDeviceLogin = runDeviceLogin;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const DEFAULT_INTERVAL_SEC = 5;
|
|
9
|
+
const DEFAULT_EXPIRES_SEC = 1800; // ~30 分
|
|
10
|
+
function defaultSleep(ms) {
|
|
11
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 非対話環境(Claude Code 等)向けの Google device flow ログイン。
|
|
15
|
+
*
|
|
16
|
+
* サーバー(/api/auth/cli/google/device/start)で device code を取得し、
|
|
17
|
+
* ユーザーに verification_url と user_code を提示 → ユーザーがブラウザで許可 →
|
|
18
|
+
* サーバー(/device/poll)が client_secret を使って token 交換を代行し、
|
|
19
|
+
* 完了すると access/refresh を得て保存する。CLI は client_secret を一切扱わない。
|
|
20
|
+
* device_code は Google のワンタイム値なのでメモリ保持のみ(保存しない)。
|
|
21
|
+
*/
|
|
22
|
+
async function runDeviceLogin(input) {
|
|
23
|
+
const sleep = input.sleep ?? defaultSleep;
|
|
24
|
+
const log = input.log ?? ((m) => console.log(m)); // eslint-disable-line no-console
|
|
25
|
+
const now = input.now ?? (() => Date.now());
|
|
26
|
+
// 1. device code 取得(サーバー代行)
|
|
27
|
+
let start;
|
|
28
|
+
try {
|
|
29
|
+
const res = await axios_1.default.post(`${input.endpoint}/api/auth/cli/google/device/start`, {}, { timeout: 15000, headers: { 'Content-Type': 'application/json' } });
|
|
30
|
+
start = res.data ?? {};
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const status = err?.response?.status;
|
|
34
|
+
if (status === 404) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
error: 'device_not_configured: サーバー側で device flow が無効(sso_disabled)です。' +
|
|
38
|
+
'管理者に GOOGLE_SSO_DEVICE_CLIENT_ID / GOOGLE_SSO_DEVICE_CLIENT_SECRET の設定を依頼してください',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return { ok: false, error: 'device_start_failed' };
|
|
42
|
+
}
|
|
43
|
+
const { device_code, user_code, verification_url } = start;
|
|
44
|
+
if (!device_code || !user_code || !verification_url) {
|
|
45
|
+
return { ok: false, error: 'device_start_failed' };
|
|
46
|
+
}
|
|
47
|
+
let intervalSec = typeof start.interval === 'number' && start.interval > 0 ? start.interval : DEFAULT_INTERVAL_SEC;
|
|
48
|
+
const expiresSec = typeof start.expires_in === 'number' && start.expires_in > 0 ? start.expires_in : DEFAULT_EXPIRES_SEC;
|
|
49
|
+
const deadline = now() + expiresSec * 1000;
|
|
50
|
+
// 2. ユーザーに案内(user_code を目立たせる)
|
|
51
|
+
log('');
|
|
52
|
+
log('────────────────────────────────────────────');
|
|
53
|
+
log(' Google でログインしてください(ブラウザ操作が必要です)');
|
|
54
|
+
log('');
|
|
55
|
+
log(` 1. ブラウザで次の URL を開く: ${verification_url}`);
|
|
56
|
+
log(` 2. 次のコードを入力して許可:`);
|
|
57
|
+
log('');
|
|
58
|
+
log(` ┌─────────────────┐`);
|
|
59
|
+
log(` │ ${user_code} │`);
|
|
60
|
+
log(` └─────────────────┘`);
|
|
61
|
+
log('');
|
|
62
|
+
log(' 許可が完了すると自動でログインが続行されます…');
|
|
63
|
+
log('────────────────────────────────────────────');
|
|
64
|
+
log('');
|
|
65
|
+
// 3. poll ループ
|
|
66
|
+
for (;;) {
|
|
67
|
+
if (now() >= deadline) {
|
|
68
|
+
return { ok: false, error: 'timeout' };
|
|
69
|
+
}
|
|
70
|
+
await sleep(intervalSec * 1000);
|
|
71
|
+
let poll;
|
|
72
|
+
try {
|
|
73
|
+
const res = await axios_1.default.post(`${input.endpoint}/api/auth/cli/google/device/poll`, { device_code }, { timeout: 15000, headers: { 'Content-Type': 'application/json' } });
|
|
74
|
+
poll = res.data ?? {};
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// poll のネットワークエラーはリトライ扱い(サーバーは正常系を 200 で返す設計)
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
switch (poll.status) {
|
|
81
|
+
case 'pending':
|
|
82
|
+
continue;
|
|
83
|
+
case 'slow_down':
|
|
84
|
+
intervalSec += 5;
|
|
85
|
+
continue;
|
|
86
|
+
case 'denied':
|
|
87
|
+
return { ok: false, error: 'access_denied' };
|
|
88
|
+
case 'expired':
|
|
89
|
+
return { ok: false, error: 'expired' };
|
|
90
|
+
case 'complete': {
|
|
91
|
+
if (!poll.access_token || !poll.refresh_token) {
|
|
92
|
+
return { ok: false, error: 'invalid_response' };
|
|
93
|
+
}
|
|
94
|
+
const expiresAt = new Date(now() + (poll.expires_in ?? 3600) * 1000).toISOString();
|
|
95
|
+
const creds = {
|
|
96
|
+
endpoint: input.endpoint,
|
|
97
|
+
accessToken: poll.access_token,
|
|
98
|
+
refreshToken: poll.refresh_token,
|
|
99
|
+
expiresAt,
|
|
100
|
+
};
|
|
101
|
+
input.store.save(creds);
|
|
102
|
+
return { ok: true };
|
|
103
|
+
}
|
|
104
|
+
case 'error':
|
|
105
|
+
default:
|
|
106
|
+
return { ok: false, error: poll.reason ?? 'poll_failed' };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=authDevice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authDevice.js","sourceRoot":"","sources":["../../src/commands/authDevice.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runGoogleLogin = runGoogleLogin;
|
|
7
|
+
const http_1 = __importDefault(require("http"));
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const axios_1 = __importDefault(require("axios"));
|
|
11
|
+
const AUTH_ENDPOINT = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
12
|
+
const ALLOWED_HD = process.env.WEBGRAM_GOOGLE_HD || 'webgram.jp';
|
|
13
|
+
const CALLBACK_TIMEOUT_MS = 5 * 60 * 1000; // 5 分
|
|
14
|
+
const RESULT_HTML = '<!doctype html><html lang="ja"><head><meta charset="utf-8">' +
|
|
15
|
+
'<title>webgram CLI</title></head><body style="font-family:sans-serif;padding:2rem">' +
|
|
16
|
+
'<h2>ログインが完了しました。</h2><p>ターミナルに戻ってください。</p></body></html>';
|
|
17
|
+
/**
|
|
18
|
+
* OS のデフォルトブラウザで URL を開く。失敗しても throw せず false を返す
|
|
19
|
+
* (呼び出し側で URL を手動オープン誘導する)。
|
|
20
|
+
*/
|
|
21
|
+
function openBrowser(url) {
|
|
22
|
+
try {
|
|
23
|
+
let cmd;
|
|
24
|
+
let args;
|
|
25
|
+
if (process.platform === 'darwin') {
|
|
26
|
+
cmd = 'open';
|
|
27
|
+
args = [url];
|
|
28
|
+
}
|
|
29
|
+
else if (process.platform === 'win32') {
|
|
30
|
+
// start は cmd の組み込みコマンド。空タイトル引数 "" を挟むと URL の & 等で崩れにくい
|
|
31
|
+
cmd = 'cmd';
|
|
32
|
+
args = ['/c', 'start', '', url];
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
cmd = 'xdg-open';
|
|
36
|
+
args = [url];
|
|
37
|
+
}
|
|
38
|
+
const child = (0, child_process_1.spawn)(cmd, args, { stdio: 'ignore', detached: true });
|
|
39
|
+
child.on('error', () => {
|
|
40
|
+
/* spawn 失敗は無視(戻り値では拾えないので呼び出し側は URL 表示も併用する) */
|
|
41
|
+
});
|
|
42
|
+
child.unref();
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* webgram auth login(Google loopback flow)のコア動作。
|
|
51
|
+
*
|
|
52
|
+
* ローカル 127.0.0.1 の一時ポートで受け口を立て、ブラウザで Google 認可 →
|
|
53
|
+
* 認可コード + PKCE code_verifier + redirect_uri を ad-dashboard の
|
|
54
|
+
* POST /api/auth/cli/google に渡す。Google との code→token 交換は
|
|
55
|
+
* サーバーが client_secret を使って実行する(CLI には secret を一切持たせない)。
|
|
56
|
+
* サーバー発行の access/refresh を得て保存する。
|
|
57
|
+
*
|
|
58
|
+
* クライアント ID は公開情報のため CLI に焼き込まず、
|
|
59
|
+
* input.clientId > env WEBGRAM_GOOGLE_CLIENT_ID > サーバー取得(/google/config)の順で解決する。
|
|
60
|
+
*/
|
|
61
|
+
async function runGoogleLogin(input) {
|
|
62
|
+
let clientId = input.clientId || process.env.WEBGRAM_GOOGLE_CLIENT_ID;
|
|
63
|
+
// どちらも無ければサーバーからクライアント ID を取得(単一情報源=サーバー env)
|
|
64
|
+
if (!clientId) {
|
|
65
|
+
try {
|
|
66
|
+
const cfgRes = await axios_1.default.get(`${input.endpoint}/api/auth/cli/google/config`, {
|
|
67
|
+
timeout: 15000,
|
|
68
|
+
});
|
|
69
|
+
clientId = cfgRes.data?.clientId;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* 取得失敗(404 sso_disabled 等)は下の未設定エラーで扱う */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!clientId) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
error: 'google_client_not_configured: Google クライアント ID が取得できませんでした。' +
|
|
79
|
+
'サーバー側で SSO が無効(sso_disabled)の可能性があります。' +
|
|
80
|
+
'管理者に GOOGLE_SSO_CLI_CLIENT_ID / GOOGLE_SSO_CLI_CLIENT_SECRET の設定を依頼してください',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// PKCE + state
|
|
84
|
+
const codeVerifier = crypto_1.default.randomBytes(32).toString('base64url');
|
|
85
|
+
const codeChallenge = crypto_1.default.createHash('sha256').update(codeVerifier).digest('base64url');
|
|
86
|
+
const state = crypto_1.default.randomBytes(16).toString('base64url');
|
|
87
|
+
// 127.0.0.1 の OS 割当ポートで受け口を立てる
|
|
88
|
+
const server = http_1.default.createServer();
|
|
89
|
+
await new Promise((resolve, reject) => {
|
|
90
|
+
server.once('error', reject);
|
|
91
|
+
server.listen(0, '127.0.0.1', () => resolve());
|
|
92
|
+
});
|
|
93
|
+
const port = server.address().port;
|
|
94
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
95
|
+
const params = new URLSearchParams({
|
|
96
|
+
client_id: clientId,
|
|
97
|
+
redirect_uri: redirectUri,
|
|
98
|
+
response_type: 'code',
|
|
99
|
+
scope: 'openid email profile',
|
|
100
|
+
state,
|
|
101
|
+
code_challenge: codeChallenge,
|
|
102
|
+
code_challenge_method: 'S256',
|
|
103
|
+
access_type: 'online',
|
|
104
|
+
prompt: 'select_account',
|
|
105
|
+
hd: ALLOWED_HD,
|
|
106
|
+
});
|
|
107
|
+
const authUrl = `${AUTH_ENDPOINT}?${params.toString()}`;
|
|
108
|
+
// 認可コードを待つ(タイムアウト付き)。secret を含まないので URL 表示は安全。
|
|
109
|
+
const codePromise = new Promise((resolve) => {
|
|
110
|
+
let settled = false;
|
|
111
|
+
const finish = (r) => {
|
|
112
|
+
if (settled)
|
|
113
|
+
return;
|
|
114
|
+
settled = true;
|
|
115
|
+
resolve(r);
|
|
116
|
+
};
|
|
117
|
+
const timer = setTimeout(() => {
|
|
118
|
+
finish({ error: 'timeout: 5 分以内に認可が完了しませんでした' });
|
|
119
|
+
}, CALLBACK_TIMEOUT_MS);
|
|
120
|
+
timer.unref?.();
|
|
121
|
+
server.on('request', (req, res) => {
|
|
122
|
+
const url = new URL(req.url || '/', `http://127.0.0.1:${port}`);
|
|
123
|
+
if (url.pathname !== '/callback') {
|
|
124
|
+
res.writeHead(404);
|
|
125
|
+
res.end();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const returnedState = url.searchParams.get('state');
|
|
129
|
+
const googleError = url.searchParams.get('error');
|
|
130
|
+
const code = url.searchParams.get('code');
|
|
131
|
+
// state 照合(不一致は即エラー)
|
|
132
|
+
if (!returnedState || returnedState !== state) {
|
|
133
|
+
res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
134
|
+
res.end('state mismatch');
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
finish({ error: 'state_mismatch' });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (googleError) {
|
|
140
|
+
res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
141
|
+
res.end('authorization denied');
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
finish({ error: `google_error: ${googleError}` });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (!code) {
|
|
147
|
+
res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
148
|
+
res.end('missing code');
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
finish({ error: 'missing_code' });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
154
|
+
res.end(RESULT_HTML);
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
finish({ code });
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
// ブラウザ起動。失敗時は URL を返して手動オープン誘導。
|
|
160
|
+
const opened = openBrowser(authUrl);
|
|
161
|
+
let outcome;
|
|
162
|
+
try {
|
|
163
|
+
outcome = await codePromise;
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
server.close();
|
|
167
|
+
}
|
|
168
|
+
if ('error' in outcome) {
|
|
169
|
+
return { ok: false, error: outcome.error, authUrl: opened ? undefined : authUrl };
|
|
170
|
+
}
|
|
171
|
+
// 認可コード + PKCE verifier + redirect_uri を ad-dashboard に渡す。
|
|
172
|
+
// Google との code→token 交換はサーバーが client_secret を使って実行する。
|
|
173
|
+
try {
|
|
174
|
+
const res = await axios_1.default.post(`${input.endpoint}/api/auth/cli/google`, { code: outcome.code, code_verifier: codeVerifier, redirect_uri: redirectUri }, { timeout: 15000, headers: { 'Content-Type': 'application/json' } });
|
|
175
|
+
const { access_token, refresh_token, expires_in } = res.data ?? {};
|
|
176
|
+
if (!access_token || !refresh_token) {
|
|
177
|
+
return { ok: false, error: 'invalid_response' };
|
|
178
|
+
}
|
|
179
|
+
const expiresAt = new Date(Date.now() + (expires_in ?? 3600) * 1000).toISOString();
|
|
180
|
+
const creds = {
|
|
181
|
+
endpoint: input.endpoint,
|
|
182
|
+
accessToken: access_token,
|
|
183
|
+
refreshToken: refresh_token,
|
|
184
|
+
expiresAt,
|
|
185
|
+
};
|
|
186
|
+
input.store.save(creds);
|
|
187
|
+
return { ok: true };
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
const data = err?.response
|
|
191
|
+
?.data;
|
|
192
|
+
const code = data?.reason ?? data?.error ?? 'network_error';
|
|
193
|
+
return { ok: false, error: code };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=authGoogle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authGoogle.js","sourceRoot":"","sources":["../../src/commands/authGoogle.ts"],"names":[],"mappings":"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"}
|