@getbrevo/cli 0.0.1-alpha.1
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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +182 -0
- package/dist/api/client.d.ts +23 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +186 -0
- package/dist/api/client.js.map +1 -0
- package/dist/bin/index.d.ts +3 -0
- package/dist/bin/index.d.ts.map +1 -0
- package/dist/bin/index.js +179 -0
- package/dist/bin/index.js.map +1 -0
- package/dist/commands/app/create.d.ts +7 -0
- package/dist/commands/app/create.d.ts.map +1 -0
- package/dist/commands/app/create.js +261 -0
- package/dist/commands/app/create.js.map +1 -0
- package/dist/commands/app/credentials.d.ts +6 -0
- package/dist/commands/app/credentials.d.ts.map +1 -0
- package/dist/commands/app/credentials.js +106 -0
- package/dist/commands/app/credentials.js.map +1 -0
- package/dist/commands/app/delete.d.ts +6 -0
- package/dist/commands/app/delete.d.ts.map +1 -0
- package/dist/commands/app/delete.js +146 -0
- package/dist/commands/app/delete.js.map +1 -0
- package/dist/commands/app/list.d.ts +4 -0
- package/dist/commands/app/list.d.ts.map +1 -0
- package/dist/commands/app/list.js +46 -0
- package/dist/commands/app/list.js.map +1 -0
- package/dist/commands/app/scaffold.d.ts +5 -0
- package/dist/commands/app/scaffold.d.ts.map +1 -0
- package/dist/commands/app/scaffold.js +199 -0
- package/dist/commands/app/scaffold.js.map +1 -0
- package/dist/commands/app/start.d.ts +5 -0
- package/dist/commands/app/start.d.ts.map +1 -0
- package/dist/commands/app/start.js +138 -0
- package/dist/commands/app/start.js.map +1 -0
- package/dist/commands/app/test.d.ts +6 -0
- package/dist/commands/app/test.d.ts.map +1 -0
- package/dist/commands/app/test.js +495 -0
- package/dist/commands/app/test.js.map +1 -0
- package/dist/commands/app/update.d.ts +2 -0
- package/dist/commands/app/update.d.ts.map +1 -0
- package/dist/commands/app/update.js +324 -0
- package/dist/commands/app/update.js.map +1 -0
- package/dist/commands/definitions.d.ts +4 -0
- package/dist/commands/definitions.d.ts.map +1 -0
- package/dist/commands/definitions.js +197 -0
- package/dist/commands/definitions.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +90 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +6 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +219 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +54 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/whoami.d.ts +4 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +74 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/container.d.ts +7 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +28 -0
- package/dist/container.js.map +1 -0
- package/dist/lang/en.d.ts +110 -0
- package/dist/lang/en.d.ts.map +1 -0
- package/dist/lang/en.js +133 -0
- package/dist/lang/en.js.map +1 -0
- package/dist/lib/abort.d.ts +3 -0
- package/dist/lib/abort.d.ts.map +1 -0
- package/dist/lib/abort.js +30 -0
- package/dist/lib/abort.js.map +1 -0
- package/dist/lib/auth-guard.d.ts +3 -0
- package/dist/lib/auth-guard.d.ts.map +1 -0
- package/dist/lib/auth-guard.js +25 -0
- package/dist/lib/auth-guard.js.map +1 -0
- package/dist/lib/browser.d.ts +2 -0
- package/dist/lib/browser.d.ts.map +1 -0
- package/dist/lib/browser.js +16 -0
- package/dist/lib/browser.js.map +1 -0
- package/dist/lib/command-handler.d.ts +10 -0
- package/dist/lib/command-handler.d.ts.map +1 -0
- package/dist/lib/command-handler.js +16 -0
- package/dist/lib/command-handler.js.map +1 -0
- package/dist/lib/command-registry.d.ts +28 -0
- package/dist/lib/command-registry.d.ts.map +1 -0
- package/dist/lib/command-registry.js +54 -0
- package/dist/lib/command-registry.js.map +1 -0
- package/dist/lib/config.d.ts +76 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +314 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constants.d.ts +35 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +97 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/errors.d.ts +30 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +66 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/exit-codes.d.ts +9 -0
- package/dist/lib/exit-codes.d.ts.map +1 -0
- package/dist/lib/exit-codes.js +12 -0
- package/dist/lib/exit-codes.js.map +1 -0
- package/dist/lib/hidden-input.d.ts +2 -0
- package/dist/lib/hidden-input.d.ts.map +1 -0
- package/dist/lib/hidden-input.js +78 -0
- package/dist/lib/hidden-input.js.map +1 -0
- package/dist/lib/json-output.d.ts +6 -0
- package/dist/lib/json-output.d.ts.map +1 -0
- package/dist/lib/json-output.js +11 -0
- package/dist/lib/json-output.js.map +1 -0
- package/dist/lib/logger.d.ts +11 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +87 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/port.d.ts +12 -0
- package/dist/lib/port.d.ts.map +1 -0
- package/dist/lib/port.js +79 -0
- package/dist/lib/port.js.map +1 -0
- package/dist/lib/ui.d.ts +14 -0
- package/dist/lib/ui.d.ts.map +1 -0
- package/dist/lib/ui.js +75 -0
- package/dist/lib/ui.js.map +1 -0
- package/dist/lib/validators.d.ts +32 -0
- package/dist/lib/validators.d.ts.map +1 -0
- package/dist/lib/validators.js +96 -0
- package/dist/lib/validators.js.map +1 -0
- package/dist/services/account.d.ts +8 -0
- package/dist/services/account.d.ts.map +1 -0
- package/dist/services/account.js +15 -0
- package/dist/services/account.js.map +1 -0
- package/dist/services/app.d.ts +35 -0
- package/dist/services/app.d.ts.map +1 -0
- package/dist/services/app.js +117 -0
- package/dist/services/app.js.map +1 -0
- package/dist/services/browser-auth.d.ts +15 -0
- package/dist/services/browser-auth.d.ts.map +1 -0
- package/dist/services/browser-auth.js +187 -0
- package/dist/services/browser-auth.js.map +1 -0
- package/dist/services/normalize-app-id.d.ts +12 -0
- package/dist/services/normalize-app-id.d.ts.map +1 -0
- package/dist/services/normalize-app-id.js +38 -0
- package/dist/services/normalize-app-id.js.map +1 -0
- package/dist/services/oauth-refresh.d.ts +14 -0
- package/dist/services/oauth-refresh.d.ts.map +1 -0
- package/dist/services/oauth-refresh.js +70 -0
- package/dist/services/oauth-refresh.js.map +1 -0
- package/dist/templates/files/AGENTS.md.tmpl +41 -0
- package/dist/templates/files/CLAUDE.md.tmpl +50 -0
- package/dist/templates/files/README.md.tmpl +77 -0
- package/dist/templates/files/app-config.json.tmpl +23 -0
- package/dist/templates/files/gitignore.tmpl +9 -0
- package/dist/templates/files/src/oauth/.env.example.tmpl +13 -0
- package/dist/templates/files/src/oauth/.env.local.tmpl +10 -0
- package/dist/templates/files/src/oauth/handler.js.tmpl +203 -0
- package/dist/templates/files/src/oauth/package.json.tmpl +15 -0
- package/dist/templates/files/src/oauth/server.js.tmpl +226 -0
- package/dist/templates/files/src/oauth/token-store.js.tmpl +39 -0
- package/dist/templates/index.d.ts +36 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +105 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,117 @@
|
|
|
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.createAppService = createAppService;
|
|
7
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
|
+
const constants_1 = require("../lib/constants");
|
|
9
|
+
const errors_1 = require("../lib/errors");
|
|
10
|
+
const exit_codes_1 = require("../lib/exit-codes");
|
|
11
|
+
const logger_1 = require("../lib/logger");
|
|
12
|
+
const ui_1 = require("../lib/ui");
|
|
13
|
+
const en_1 = require("../lang/en");
|
|
14
|
+
const config_1 = require("../lib/config");
|
|
15
|
+
const normalize_app_id_1 = require("./normalize-app-id");
|
|
16
|
+
/**
|
|
17
|
+
* Check apps exist and throw with user-facing message if empty.
|
|
18
|
+
*/
|
|
19
|
+
function logEmptyAndThrow() {
|
|
20
|
+
(0, logger_1.logInfo)(`\n ${en_1.messages.APP_LIST_EMPTY}\n`);
|
|
21
|
+
throw new errors_1.CliError(en_1.messages.APP_LIST_EMPTY, exit_codes_1.EXIT_CODES.ERROR);
|
|
22
|
+
}
|
|
23
|
+
function createAppService(client) {
|
|
24
|
+
async function fetchAppsList() {
|
|
25
|
+
const apps = await client.get(constants_1.ENDPOINTS.OAUTH_APPS);
|
|
26
|
+
return (apps || []).map(normalize_app_id_1.normalizeAppId);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
fetchAppsList,
|
|
30
|
+
async fetchApp(appId) {
|
|
31
|
+
const app = await client.get(constants_1.ENDPOINTS.OAUTH_APP(appId));
|
|
32
|
+
return app ? (0, normalize_app_id_1.normalizeAppId)(app) : null;
|
|
33
|
+
},
|
|
34
|
+
async pickApp(promptMessage, formatChoice) {
|
|
35
|
+
const spinner = (0, ui_1.createSpinner)('Loading apps...');
|
|
36
|
+
const apps = await fetchAppsList();
|
|
37
|
+
spinner.stop();
|
|
38
|
+
if (apps.length === 0) {
|
|
39
|
+
logEmptyAndThrow();
|
|
40
|
+
}
|
|
41
|
+
const { selectedApp } = await inquirer_1.default.prompt([
|
|
42
|
+
{
|
|
43
|
+
type: 'rawlist',
|
|
44
|
+
name: 'selectedApp',
|
|
45
|
+
message: promptMessage,
|
|
46
|
+
choices: apps.map((a) => {
|
|
47
|
+
const appName = a.name || 'App ' + a.app_id;
|
|
48
|
+
return {
|
|
49
|
+
name: formatChoice
|
|
50
|
+
? formatChoice(a)
|
|
51
|
+
: `${appName} (App ID: ${a.app_id}, Client ID: ${a.client_id})`,
|
|
52
|
+
value: a.app_id,
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
return selectedApp;
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* Fetch app from API and merge with local cache.
|
|
61
|
+
* The GET endpoint may not return client_secret (only shown at creation),
|
|
62
|
+
* so we fall back to the locally cached value.
|
|
63
|
+
*/
|
|
64
|
+
async resolveAppCredentials(appId) {
|
|
65
|
+
const raw = await client.get(constants_1.ENDPOINTS.OAUTH_APP(appId));
|
|
66
|
+
if (!raw)
|
|
67
|
+
return null;
|
|
68
|
+
const app = (0, normalize_app_id_1.normalizeAppId)(raw);
|
|
69
|
+
const local = (0, config_1.getAppCredentials)(appId);
|
|
70
|
+
const diffs = [];
|
|
71
|
+
// Merge: prefer remote values, fall back to local cache for missing fields
|
|
72
|
+
if (local) {
|
|
73
|
+
if (!app.client_id && local.clientId) {
|
|
74
|
+
app.client_id = local.clientId;
|
|
75
|
+
}
|
|
76
|
+
else if (local.clientId && local.clientId !== app.client_id) {
|
|
77
|
+
diffs.push('client_id');
|
|
78
|
+
}
|
|
79
|
+
if (!app.client_secret && local.clientSecret) {
|
|
80
|
+
app.client_secret = local.clientSecret;
|
|
81
|
+
}
|
|
82
|
+
else if (local.clientSecret && local.clientSecret !== app.client_secret) {
|
|
83
|
+
diffs.push('client_secret');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { app, diffs };
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* Save credentials to local cache.
|
|
90
|
+
* Preserves existing local values when the app has missing fields
|
|
91
|
+
* (e.g. GET endpoint doesn't return client_secret).
|
|
92
|
+
*/
|
|
93
|
+
syncAppCredentials(appId, app) {
|
|
94
|
+
const existing = (0, config_1.getAppCredentials)(appId);
|
|
95
|
+
const clientId = app.client_id || existing?.clientId;
|
|
96
|
+
const clientSecret = app.client_secret || existing?.clientSecret;
|
|
97
|
+
// Only write if we have at least a client ID and a non-empty secret
|
|
98
|
+
if (clientId && clientSecret) {
|
|
99
|
+
(0, config_1.saveAppCredentials)(appId, { clientId, clientSecret });
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
async createApp(payload) {
|
|
103
|
+
const raw = await client.post(constants_1.ENDPOINTS.OAUTH_APPS, {
|
|
104
|
+
...payload,
|
|
105
|
+
source: 'cli',
|
|
106
|
+
});
|
|
107
|
+
return (0, normalize_app_id_1.normalizeAppId)(raw);
|
|
108
|
+
},
|
|
109
|
+
async updateApp(appId, body) {
|
|
110
|
+
await client.put(constants_1.ENDPOINTS.APP_STORE_APP_UPDATE(appId), body);
|
|
111
|
+
},
|
|
112
|
+
async deleteApp(appId) {
|
|
113
|
+
await client.delete(constants_1.ENDPOINTS.OAUTH_APP(appId));
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/services/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface OAuthTokens {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
refreshToken: string;
|
|
4
|
+
expiresIn: number;
|
|
5
|
+
tokenType: string;
|
|
6
|
+
scope?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface RunBrowserLoginOptions {
|
|
9
|
+
proxyUrl: string;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
openBrowser?: (url: string) => void;
|
|
12
|
+
onWaiting?: (loginUrl: string) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function runBrowserLoginFlow(opts: RunBrowserLoginOptions): Promise<OAuthTokens>;
|
|
15
|
+
//# sourceMappingURL=browser-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-auth.d.ts","sourceRoot":"","sources":["../../src/services/browser-auth.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAsCD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CAkI5F"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runBrowserLoginFlow = runBrowserLoginFlow;
|
|
37
|
+
const http = __importStar(require("node:http"));
|
|
38
|
+
const node_crypto_1 = require("node:crypto");
|
|
39
|
+
const logger_1 = require("../lib/logger");
|
|
40
|
+
const errors_1 = require("../lib/errors");
|
|
41
|
+
const en_1 = require("../lang/en");
|
|
42
|
+
const MAX_BODY_BYTES = 16 * 1024;
|
|
43
|
+
// 5 minutes — accommodates SSO/2FA flows where the user may need to fetch a
|
|
44
|
+
// code from another device. 120s was too tight and routinely timed out mid-2FA.
|
|
45
|
+
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
46
|
+
function normalizeTokens(raw) {
|
|
47
|
+
if (typeof raw.access_token !== 'string' ||
|
|
48
|
+
!raw.access_token ||
|
|
49
|
+
typeof raw.refresh_token !== 'string' ||
|
|
50
|
+
!raw.refresh_token ||
|
|
51
|
+
typeof raw.expires_in !== 'number' ||
|
|
52
|
+
!Number.isFinite(raw.expires_in) ||
|
|
53
|
+
raw.expires_in <= 0 ||
|
|
54
|
+
typeof raw.token_type !== 'string' ||
|
|
55
|
+
!raw.token_type) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
accessToken: raw.access_token,
|
|
60
|
+
refreshToken: raw.refresh_token,
|
|
61
|
+
expiresIn: raw.expires_in,
|
|
62
|
+
tokenType: raw.token_type,
|
|
63
|
+
scope: typeof raw.scope === 'string' ? raw.scope : undefined,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function runBrowserLoginFlow(opts) {
|
|
67
|
+
const proxyOrigin = new URL(opts.proxyUrl).origin;
|
|
68
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
69
|
+
const openBrowser = opts.openBrowser ?? (() => { });
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
let settled = false;
|
|
72
|
+
const settle = (fn) => {
|
|
73
|
+
if (settled)
|
|
74
|
+
return;
|
|
75
|
+
settled = true;
|
|
76
|
+
fn();
|
|
77
|
+
};
|
|
78
|
+
const server = http.createServer((req, res) => {
|
|
79
|
+
const pathname = new URL(req.url ?? '/', 'http://127.0.0.1').pathname;
|
|
80
|
+
const origin = req.headers.origin;
|
|
81
|
+
(0, logger_1.logDebug)('loopback request', { method: req.method, url: req.url, pathname, origin });
|
|
82
|
+
if (req.method === 'OPTIONS' && pathname === '/callback') {
|
|
83
|
+
if (origin !== proxyOrigin) {
|
|
84
|
+
(0, logger_1.logDebug)('loopback OPTIONS rejected: origin mismatch', { origin, expected: proxyOrigin });
|
|
85
|
+
res.writeHead(403).end();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
res
|
|
89
|
+
.writeHead(204, {
|
|
90
|
+
'Access-Control-Allow-Origin': proxyOrigin,
|
|
91
|
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
92
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
93
|
+
'Access-Control-Max-Age': '600',
|
|
94
|
+
})
|
|
95
|
+
.end();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (req.method === 'POST' && pathname === '/callback') {
|
|
99
|
+
if (origin !== proxyOrigin) {
|
|
100
|
+
(0, logger_1.logDebug)('loopback POST rejected: origin mismatch', { origin, expected: proxyOrigin });
|
|
101
|
+
res.writeHead(403).end();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
let bytes = 0;
|
|
105
|
+
const chunks = [];
|
|
106
|
+
req.on('data', (chunk) => {
|
|
107
|
+
bytes += chunk.length;
|
|
108
|
+
if (bytes > MAX_BODY_BYTES) {
|
|
109
|
+
(0, logger_1.logDebug)('loopback POST rejected: body too large', { bytes, max: MAX_BODY_BYTES });
|
|
110
|
+
res.writeHead(413, { Connection: 'close' }).end();
|
|
111
|
+
req.destroy();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
chunks.push(chunk);
|
|
115
|
+
});
|
|
116
|
+
req.on('end', () => {
|
|
117
|
+
let parsed = null;
|
|
118
|
+
try {
|
|
119
|
+
parsed = JSON.parse(Buffer.concat(chunks).toString('utf-8'));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
parsed = null;
|
|
123
|
+
}
|
|
124
|
+
const tokens = parsed ? normalizeTokens(parsed) : null;
|
|
125
|
+
if (!tokens) {
|
|
126
|
+
(0, logger_1.logDebug)('loopback POST rejected: bad payload shape', {
|
|
127
|
+
hasParsed: parsed !== null,
|
|
128
|
+
keys: parsed ? Object.keys(parsed) : null,
|
|
129
|
+
});
|
|
130
|
+
res
|
|
131
|
+
.writeHead(400, {
|
|
132
|
+
'Access-Control-Allow-Origin': proxyOrigin,
|
|
133
|
+
'Content-Type': 'text/plain',
|
|
134
|
+
})
|
|
135
|
+
.end('Bad payload');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
(0, logger_1.logDebug)('loopback POST accepted', { hasScope: tokens.scope !== undefined });
|
|
139
|
+
res.writeHead(204, { 'Access-Control-Allow-Origin': proxyOrigin }).end();
|
|
140
|
+
settle(() => {
|
|
141
|
+
server.close();
|
|
142
|
+
resolve(tokens);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (req.method === 'GET' && (pathname === '/' || pathname === '/callback')) {
|
|
148
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
149
|
+
res.end('<!doctype html><meta charset="utf-8"><title>Brevo CLI login</title>' +
|
|
150
|
+
'<p>Waiting for login to complete — you can close this tab once the CLI confirms success.</p>');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
(0, logger_1.logDebug)('loopback request not matched', { method: req.method, pathname });
|
|
154
|
+
res.writeHead(404).end();
|
|
155
|
+
});
|
|
156
|
+
server.on('error', (err) => {
|
|
157
|
+
(0, logger_1.logDebug)('loopback server error', { message: err.message });
|
|
158
|
+
settle(() => reject(err));
|
|
159
|
+
});
|
|
160
|
+
server.listen(0, '127.0.0.1', () => {
|
|
161
|
+
const port = server.address().port;
|
|
162
|
+
// Unique per-attempt token. If a previous attempt timed out mid-flow,
|
|
163
|
+
// the browser may still have a tab open at the proxy showing an error;
|
|
164
|
+
// a unique URL forces a fresh navigation instead of reusing that tab,
|
|
165
|
+
// and prevents the proxy/browser from serving a cached response.
|
|
166
|
+
const attemptToken = (0, node_crypto_1.randomUUID)();
|
|
167
|
+
const loginUrl = `${opts.proxyUrl}/login?port=${port}&t=${attemptToken}`;
|
|
168
|
+
(0, logger_1.logDebug)('loopback listening', { host: '127.0.0.1', port, proxyOrigin });
|
|
169
|
+
opts.onWaiting?.(loginUrl);
|
|
170
|
+
try {
|
|
171
|
+
openBrowser(loginUrl);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// best-effort — URL already surfaced via onWaiting
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
const timer = setTimeout(() => {
|
|
178
|
+
settle(() => {
|
|
179
|
+
server.close();
|
|
180
|
+
reject(new errors_1.CliError(en_1.messages.AUTH_BROWSER_TIMEOUT));
|
|
181
|
+
});
|
|
182
|
+
}, timeoutMs);
|
|
183
|
+
timer.unref?.();
|
|
184
|
+
server.on('close', () => clearTimeout(timer));
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=browser-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-auth.js","sourceRoot":"","sources":["../../src/services/browser-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coerce `app_id` on an API response object to a string.
|
|
3
|
+
* Defensive boundary in case the server still returns a number for a legacy app.
|
|
4
|
+
* Fails fast on unexpected types so invalid server responses never propagate as
|
|
5
|
+
* bogus IDs (e.g. "undefined", "[object Object]") into requests or credential keys.
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeAppId<T extends {
|
|
8
|
+
app_id: unknown;
|
|
9
|
+
}>(raw: T): Omit<T, 'app_id'> & {
|
|
10
|
+
app_id: string;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=normalize-app-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-app-id.d.ts","sourceRoot":"","sources":["../../src/services/normalize-app-id.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,EAC1D,GAAG,EAAE,CAAC,GACL,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAkBxC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeAppId = normalizeAppId;
|
|
4
|
+
const errors_1 = require("../lib/errors");
|
|
5
|
+
/**
|
|
6
|
+
* Coerce `app_id` on an API response object to a string.
|
|
7
|
+
* Defensive boundary in case the server still returns a number for a legacy app.
|
|
8
|
+
* Fails fast on unexpected types so invalid server responses never propagate as
|
|
9
|
+
* bogus IDs (e.g. "undefined", "[object Object]") into requests or credential keys.
|
|
10
|
+
*/
|
|
11
|
+
function normalizeAppId(raw) {
|
|
12
|
+
const { app_id } = raw;
|
|
13
|
+
if (typeof app_id === 'string') {
|
|
14
|
+
const trimmed = app_id.trim();
|
|
15
|
+
if (trimmed.length === 0) {
|
|
16
|
+
throw new errors_1.CliError('Invalid app_id in API response: expected non-empty string, received empty string.');
|
|
17
|
+
}
|
|
18
|
+
return { ...raw, app_id: trimmed };
|
|
19
|
+
}
|
|
20
|
+
if (typeof app_id === 'number' && Number.isFinite(app_id)) {
|
|
21
|
+
return { ...raw, app_id: String(app_id) };
|
|
22
|
+
}
|
|
23
|
+
const received = describeAppId(app_id);
|
|
24
|
+
throw new errors_1.CliError(`Invalid app_id in API response: expected string or finite number, received ${received}.`);
|
|
25
|
+
}
|
|
26
|
+
function describeAppId(value) {
|
|
27
|
+
if (value === null)
|
|
28
|
+
return 'null';
|
|
29
|
+
if (typeof value === 'number') {
|
|
30
|
+
if (Number.isNaN(value))
|
|
31
|
+
return 'number (NaN)';
|
|
32
|
+
if (!Number.isFinite(value))
|
|
33
|
+
return `number (${value > 0 ? 'Infinity' : '-Infinity'})`;
|
|
34
|
+
return 'number';
|
|
35
|
+
}
|
|
36
|
+
return typeof value;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=normalize-app-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-app-id.js","sourceRoot":"","sources":["../../src/services/normalize-app-id.ts"],"names":[],"mappings":";;AAQA,wCAoBC;AA5BD,0CAAyC;AAEzC;;;;;GAKG;AACH,SAAgB,cAAc,CAC5B,GAAM;IAEN,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IACvB,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,iBAAQ,CAChB,mFAAmF,CACpF,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACrC,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1D,OAAO,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;IAC5C,CAAC;IACD,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,IAAI,iBAAQ,CAChB,8EAA8E,QAAQ,GAAG,CAC1F,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;YAAE,OAAO,cAAc,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,WAAW,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC;QACvF,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,OAAO,KAAK,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface RefreshedTokens {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
refreshToken: string;
|
|
4
|
+
expiresIn: number;
|
|
5
|
+
tokenType: string;
|
|
6
|
+
scope?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class RefreshError extends Error {
|
|
9
|
+
readonly unauthorized: boolean;
|
|
10
|
+
readonly cause?: unknown | undefined;
|
|
11
|
+
constructor(message: string, unauthorized: boolean, cause?: unknown | undefined);
|
|
12
|
+
}
|
|
13
|
+
export declare function refreshAccessToken(refreshToken: string, proxyUrl: string): Promise<RefreshedTokens>;
|
|
14
|
+
//# sourceMappingURL=oauth-refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-refresh.d.ts","sourceRoot":"","sources":["../../src/services/oauth-refresh.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,YAAa,SAAQ,KAAK;aAGnB,YAAY,EAAE,OAAO;aACrB,KAAK,CAAC,EAAE,OAAO;gBAF/B,OAAO,EAAE,MAAM,EACC,YAAY,EAAE,OAAO,EACrB,KAAK,CAAC,EAAE,OAAO,YAAA;CAKlC;AAED,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,CAAC,CAgE1B"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RefreshError = void 0;
|
|
4
|
+
exports.refreshAccessToken = refreshAccessToken;
|
|
5
|
+
class RefreshError extends Error {
|
|
6
|
+
unauthorized;
|
|
7
|
+
cause;
|
|
8
|
+
constructor(message, unauthorized, cause) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.unauthorized = unauthorized;
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
this.name = 'RefreshError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.RefreshError = RefreshError;
|
|
16
|
+
async function refreshAccessToken(refreshToken, proxyUrl) {
|
|
17
|
+
let res;
|
|
18
|
+
try {
|
|
19
|
+
res = await fetch(`${proxyUrl}/refresh`, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
Accept: 'application/json',
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
26
|
+
signal: AbortSignal.timeout(15_000),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
throw new RefreshError('Could not reach the Brevo login service.', false, err);
|
|
31
|
+
}
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new RefreshError(`Token refresh failed (${res.status}).`, res.status === 401);
|
|
34
|
+
}
|
|
35
|
+
let body;
|
|
36
|
+
try {
|
|
37
|
+
body = (await res.json());
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
throw new RefreshError('Malformed refresh response from login service.', false, err);
|
|
41
|
+
}
|
|
42
|
+
if (!body.access_token ||
|
|
43
|
+
typeof body.access_token !== 'string' ||
|
|
44
|
+
typeof body.expires_in !== 'number' ||
|
|
45
|
+
!Number.isFinite(body.expires_in) ||
|
|
46
|
+
body.expires_in <= 0 ||
|
|
47
|
+
!body.token_type ||
|
|
48
|
+
typeof body.token_type !== 'string') {
|
|
49
|
+
throw new RefreshError('Malformed refresh response from login service.', false);
|
|
50
|
+
}
|
|
51
|
+
// refresh_token rotation is optional (proxies often only mint a new one
|
|
52
|
+
// when the old one is close to expiry). When the field is present it must
|
|
53
|
+
// be a non-empty string — `??` would let an empty string slip through and
|
|
54
|
+
// get persisted as a permanently-broken refresh token.
|
|
55
|
+
let resolvedRefreshToken = refreshToken;
|
|
56
|
+
if (body.refresh_token !== undefined) {
|
|
57
|
+
if (typeof body.refresh_token !== 'string' || body.refresh_token.length === 0) {
|
|
58
|
+
throw new RefreshError('Malformed refresh response from login service.', false);
|
|
59
|
+
}
|
|
60
|
+
resolvedRefreshToken = body.refresh_token;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
accessToken: body.access_token,
|
|
64
|
+
refreshToken: resolvedRefreshToken,
|
|
65
|
+
expiresIn: body.expires_in,
|
|
66
|
+
tokenType: body.token_type,
|
|
67
|
+
scope: body.scope,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=oauth-refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-refresh.js","sourceRoot":"","sources":["../../src/services/oauth-refresh.ts"],"names":[],"mappings":";;;AAmBA,gDAmEC;AA9ED,MAAa,YAAa,SAAQ,KAAK;IAGnB;IACA;IAHlB,YACE,OAAe,EACC,YAAqB,EACrB,KAAe;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,iBAAY,GAAZ,YAAY,CAAS;QACrB,UAAK,GAAL,KAAK,CAAU;QAG/B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AATD,oCASC;AAEM,KAAK,UAAU,kBAAkB,CACtC,YAAoB,EACpB,QAAgB;IAEhB,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,UAAU,EAAE;YACvC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,MAAM,EAAE,kBAAkB;aAC3B;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;YACrD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;SACpC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,YAAY,CAAC,0CAA0C,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACjF,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,YAAY,CAAC,yBAAyB,GAAG,CAAC,MAAM,IAAI,EAAE,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC;IACtF,CAAC;IAED,IAAI,IAMH,CAAC;IACF,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAgB,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,YAAY,CAAC,gDAAgD,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACvF,CAAC;IAED,IACE,CAAC,IAAI,CAAC,YAAY;QAClB,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ;QACrC,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ;QACnC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC;QACjC,IAAI,CAAC,UAAU,IAAI,CAAC;QACpB,CAAC,IAAI,CAAC,UAAU;QAChB,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,EACnC,CAAC;QACD,MAAM,IAAI,YAAY,CAAC,gDAAgD,EAAE,KAAK,CAAC,CAAC;IAClF,CAAC;IAED,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E,uDAAuD;IACvD,IAAI,oBAAoB,GAAG,YAAY,CAAC;IACxC,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACrC,IAAI,OAAO,IAAI,CAAC,aAAa,KAAK,QAAQ,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9E,MAAM,IAAI,YAAY,CAAC,gDAAgD,EAAE,KAAK,CAAC,CAAC;QAClF,CAAC;QACD,oBAAoB,GAAG,IAAI,CAAC,aAAa,CAAC;IAC5C,CAAC;IAED,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,YAAY;QAC9B,YAAY,EAAE,oBAAoB;QAClC,SAAS,EAAE,IAAI,CAAC,UAAU;QAC1B,SAAS,EAAE,IAAI,CAAC,UAAU;QAC1B,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# {{APP_NAME}} — Agent Context
|
|
2
|
+
|
|
3
|
+
This project is a Brevo OAuth 2.0 integration scaffolded with the Brevo CLI.
|
|
4
|
+
|
|
5
|
+
> **This is a test app** for local OAuth testing. Not for production use.
|
|
6
|
+
|
|
7
|
+
## What this does
|
|
8
|
+
Implements the authorization code flow to obtain access tokens for the Brevo API.
|
|
9
|
+
|
|
10
|
+
## Project structure
|
|
11
|
+
```
|
|
12
|
+
src/
|
|
13
|
+
oauth/ Self-contained OAuth test feature
|
|
14
|
+
package.json Dependencies and scripts
|
|
15
|
+
server.js Test server — Express app + dotenv + routes
|
|
16
|
+
handler.js OAuth routes (/auth/login, /auth/callback)
|
|
17
|
+
token-store.js In-memory token management (swap for DB in production)
|
|
18
|
+
.env.example Credential template (safe to commit)
|
|
19
|
+
.env.local Local credentials (gitignored — never commit)
|
|
20
|
+
app-config.json App metadata (app ID, redirect URIs, permissions)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## OAuth server
|
|
24
|
+
- OAuth uses realm-based paths: `/realms/{realm}/oauth/...`
|
|
25
|
+
- Default realm: `partner`
|
|
26
|
+
- Token endpoint expects `application/x-www-form-urlencoded` body (not query params)
|
|
27
|
+
- Supported grant types: `authorization_code`, `refresh_token`, `client_credentials`
|
|
28
|
+
|
|
29
|
+
## OAuth flow
|
|
30
|
+
1. `/auth/login` — generates CSRF state, redirects user to `{OAUTH_BASE}/realms/{realm}/oauth/authorize`
|
|
31
|
+
2. User authorizes -> OAuth server redirects to `/auth/callback` with an auth code
|
|
32
|
+
3. Server validates state, exchanges code for access + refresh tokens via `POST /realms/{realm}/oauth/token`
|
|
33
|
+
4. Verifies connection via `GET /realms/{realm}/oauth/userinfo`
|
|
34
|
+
5. `/auth/refresh` — refreshes tokens using `refresh_token` grant
|
|
35
|
+
|
|
36
|
+
## Development
|
|
37
|
+
```bash
|
|
38
|
+
yarn --cwd src/oauth
|
|
39
|
+
# or: npm --prefix src/oauth install
|
|
40
|
+
brevo app start oauth # http://localhost:3009
|
|
41
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# CLAUDE.md — {{APP_NAME}}
|
|
2
|
+
|
|
3
|
+
## Project
|
|
4
|
+
Brevo OAuth integration for app `{{CLIENT_ID}}`.
|
|
5
|
+
Scaffolded with the [Brevo CLI](https://github.com/getbrevo/brevo-cli).
|
|
6
|
+
|
|
7
|
+
> **This is a test app** scaffolded by the Brevo CLI to help you test your OAuth integration locally.
|
|
8
|
+
|
|
9
|
+
## Run
|
|
10
|
+
```bash
|
|
11
|
+
yarn --cwd src/oauth
|
|
12
|
+
# or: npm --prefix src/oauth install
|
|
13
|
+
brevo app start oauth # starts OAuth test server on http://localhost:3009
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Structure
|
|
17
|
+
```
|
|
18
|
+
src/
|
|
19
|
+
oauth/ Self-contained OAuth test feature
|
|
20
|
+
package.json Dependencies and scripts
|
|
21
|
+
server.js Test server entry point
|
|
22
|
+
handler.js OAuth routes (/auth/login, /auth/callback)
|
|
23
|
+
token-store.js In-memory token store
|
|
24
|
+
.env.example Credential template (committed)
|
|
25
|
+
.env.local Local credentials (gitignored — never commit)
|
|
26
|
+
app-config.json App metadata
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## OAuth server
|
|
30
|
+
- OAuth endpoints use realm-based paths: `/realms/{realm}/oauth/...`
|
|
31
|
+
- Default realm: `partner`
|
|
32
|
+
- Env vars `OAUTH_BASE` and `OAUTH_REALM` control the OAuth server and realm
|
|
33
|
+
|
|
34
|
+
### Endpoints used
|
|
35
|
+
| Endpoint | Purpose |
|
|
36
|
+
|----------|---------|
|
|
37
|
+
| `GET /realms/{realm}/oauth/authorize` | Authorization (redirect user here) |
|
|
38
|
+
| `POST /realms/{realm}/oauth/token` | Exchange code or refresh token for access token |
|
|
39
|
+
| `GET /realms/{realm}/oauth/userinfo` | Fetch user profile with Bearer token |
|
|
40
|
+
| `POST /realms/{realm}/oauth/revoke` | Revoke a token |
|
|
41
|
+
| `POST /realms/{realm}/oauth/introspect` | Token introspection (RFC 7662) |
|
|
42
|
+
| `GET /realms/{realm}/.well-known/openid-configuration` | OIDC discovery |
|
|
43
|
+
|
|
44
|
+
## Key flows
|
|
45
|
+
- `/auth/login` — generates random CSRF state, redirects to OAuth authorize endpoint
|
|
46
|
+
- `/auth/callback` — validates state, POSTs code to `/realms/{realm}/oauth/token`, stores tokens
|
|
47
|
+
- `/auth/refresh` — refreshes tokens using `refresh_token` grant
|
|
48
|
+
|
|
49
|
+
## Next steps
|
|
50
|
+
- Replace in-memory token store with a real database
|