@getbrevo/cli 2.0.2 → 2.1.0
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 +209 -0
- package/README.md +8 -5
- package/agent-context/AGENTS.md +59 -15
- package/agent-context/SKILL.md +70 -14
- package/dist/{templates → bin}/files/AGENTS.md.tmpl +11 -1
- package/dist/{templates → bin}/files/CLAUDE.md.tmpl +8 -0
- package/dist/{templates → bin}/files/README.md.tmpl +11 -14
- package/dist/bin/files/app-config.json.tmpl +16 -0
- package/dist/{templates → bin}/files/src/oauth/.env.example.tmpl +8 -0
- package/dist/{templates → bin}/files/src/oauth/.env.local.tmpl +2 -0
- package/dist/{templates → bin}/files/src/oauth/handler.js.tmpl +49 -7
- package/dist/{templates → bin}/files/src/oauth/server.js.tmpl +20 -14
- package/dist/{templates → bin}/files/src/oauth/token-store.js.tmpl +19 -0
- package/dist/bin/index.js +660 -235
- package/dist/bin/index.js.map +7 -1
- package/package.json +6 -4
- package/dist/api/client.d.ts +0 -25
- package/dist/api/client.d.ts.map +0 -1
- package/dist/api/client.js +0 -205
- package/dist/api/client.js.map +0 -1
- package/dist/bin/index.d.ts +0 -3
- package/dist/bin/index.d.ts.map +0 -1
- package/dist/commands/app/create.d.ts +0 -8
- package/dist/commands/app/create.d.ts.map +0 -1
- package/dist/commands/app/create.js +0 -325
- package/dist/commands/app/create.js.map +0 -1
- package/dist/commands/app/credentials.d.ts +0 -6
- package/dist/commands/app/credentials.d.ts.map +0 -1
- package/dist/commands/app/credentials.js +0 -114
- package/dist/commands/app/credentials.js.map +0 -1
- package/dist/commands/app/delete.d.ts +0 -6
- package/dist/commands/app/delete.d.ts.map +0 -1
- package/dist/commands/app/delete.js +0 -167
- package/dist/commands/app/delete.js.map +0 -1
- package/dist/commands/app/list.d.ts +0 -4
- package/dist/commands/app/list.d.ts.map +0 -1
- package/dist/commands/app/list.js +0 -71
- package/dist/commands/app/list.js.map +0 -1
- package/dist/commands/app/scaffold.d.ts +0 -5
- package/dist/commands/app/scaffold.d.ts.map +0 -1
- package/dist/commands/app/scaffold.js +0 -213
- package/dist/commands/app/scaffold.js.map +0 -1
- package/dist/commands/app/scopes.d.ts +0 -2
- package/dist/commands/app/scopes.d.ts.map +0 -1
- package/dist/commands/app/scopes.js +0 -81
- package/dist/commands/app/scopes.js.map +0 -1
- package/dist/commands/app/start.d.ts +0 -5
- package/dist/commands/app/start.d.ts.map +0 -1
- package/dist/commands/app/start.js +0 -220
- package/dist/commands/app/start.js.map +0 -1
- package/dist/commands/app/update.d.ts +0 -2
- package/dist/commands/app/update.d.ts.map +0 -1
- package/dist/commands/app/update.js +0 -444
- package/dist/commands/app/update.js.map +0 -1
- package/dist/commands/definitions.d.ts +0 -5
- package/dist/commands/definitions.d.ts.map +0 -1
- package/dist/commands/definitions.js +0 -262
- package/dist/commands/definitions.js.map +0 -1
- package/dist/commands/init.d.ts +0 -2
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -138
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/login.d.ts +0 -6
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js +0 -251
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts +0 -5
- package/dist/commands/logout.d.ts.map +0 -1
- package/dist/commands/logout.js +0 -54
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/skill/install.d.ts +0 -4
- package/dist/commands/skill/install.d.ts.map +0 -1
- package/dist/commands/skill/install.js +0 -29
- package/dist/commands/skill/install.js.map +0 -1
- package/dist/commands/skill/uninstall.d.ts +0 -4
- package/dist/commands/skill/uninstall.d.ts.map +0 -1
- package/dist/commands/skill/uninstall.js +0 -23
- package/dist/commands/skill/uninstall.js.map +0 -1
- package/dist/commands/whoami.d.ts +0 -4
- package/dist/commands/whoami.d.ts.map +0 -1
- package/dist/commands/whoami.js +0 -85
- package/dist/commands/whoami.js.map +0 -1
- package/dist/container.d.ts +0 -7
- package/dist/container.d.ts.map +0 -1
- package/dist/container.js +0 -28
- package/dist/container.js.map +0 -1
- package/dist/lang/en.d.ts +0 -162
- package/dist/lang/en.d.ts.map +0 -1
- package/dist/lang/en.js +0 -189
- package/dist/lang/en.js.map +0 -1
- package/dist/lib/abort.d.ts +0 -3
- package/dist/lib/abort.d.ts.map +0 -1
- package/dist/lib/abort.js +0 -30
- package/dist/lib/abort.js.map +0 -1
- package/dist/lib/auth-guard.d.ts +0 -13
- package/dist/lib/auth-guard.d.ts.map +0 -1
- package/dist/lib/auth-guard.js +0 -60
- package/dist/lib/auth-guard.js.map +0 -1
- package/dist/lib/browser.d.ts +0 -2
- package/dist/lib/browser.d.ts.map +0 -1
- package/dist/lib/browser.js +0 -16
- package/dist/lib/browser.js.map +0 -1
- package/dist/lib/cli-version.d.ts +0 -2
- package/dist/lib/cli-version.d.ts.map +0 -1
- package/dist/lib/cli-version.js +0 -59
- package/dist/lib/cli-version.js.map +0 -1
- package/dist/lib/command-handler.d.ts +0 -10
- package/dist/lib/command-handler.d.ts.map +0 -1
- package/dist/lib/command-handler.js +0 -16
- package/dist/lib/command-handler.js.map +0 -1
- package/dist/lib/command-registry.d.ts +0 -28
- package/dist/lib/command-registry.d.ts.map +0 -1
- package/dist/lib/command-registry.js +0 -54
- package/dist/lib/command-registry.js.map +0 -1
- package/dist/lib/config.d.ts +0 -83
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js +0 -430
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/constants.d.ts +0 -48
- package/dist/lib/constants.d.ts.map +0 -1
- package/dist/lib/constants.js +0 -121
- package/dist/lib/constants.js.map +0 -1
- package/dist/lib/errors.d.ts +0 -30
- package/dist/lib/errors.d.ts.map +0 -1
- package/dist/lib/errors.js +0 -66
- package/dist/lib/errors.js.map +0 -1
- package/dist/lib/exit-codes.d.ts +0 -9
- package/dist/lib/exit-codes.d.ts.map +0 -1
- package/dist/lib/exit-codes.js +0 -12
- package/dist/lib/exit-codes.js.map +0 -1
- package/dist/lib/hidden-input.d.ts +0 -2
- package/dist/lib/hidden-input.d.ts.map +0 -1
- package/dist/lib/hidden-input.js +0 -78
- package/dist/lib/hidden-input.js.map +0 -1
- package/dist/lib/json-output.d.ts +0 -6
- package/dist/lib/json-output.d.ts.map +0 -1
- package/dist/lib/json-output.js +0 -11
- package/dist/lib/json-output.js.map +0 -1
- package/dist/lib/logger.d.ts +0 -11
- package/dist/lib/logger.d.ts.map +0 -1
- package/dist/lib/logger.js +0 -110
- package/dist/lib/logger.js.map +0 -1
- package/dist/lib/oauth-freshness.d.ts +0 -30
- package/dist/lib/oauth-freshness.d.ts.map +0 -1
- package/dist/lib/oauth-freshness.js +0 -61
- package/dist/lib/oauth-freshness.js.map +0 -1
- package/dist/lib/port.d.ts +0 -25
- package/dist/lib/port.d.ts.map +0 -1
- package/dist/lib/port.js +0 -103
- package/dist/lib/port.js.map +0 -1
- package/dist/lib/telemetry.d.ts +0 -9
- package/dist/lib/telemetry.d.ts.map +0 -1
- package/dist/lib/telemetry.js +0 -57
- package/dist/lib/telemetry.js.map +0 -1
- package/dist/lib/ui.d.ts +0 -14
- package/dist/lib/ui.d.ts.map +0 -1
- package/dist/lib/ui.js +0 -75
- package/dist/lib/ui.js.map +0 -1
- package/dist/lib/update-notifier.d.ts +0 -37
- package/dist/lib/update-notifier.d.ts.map +0 -1
- package/dist/lib/update-notifier.js +0 -298
- package/dist/lib/update-notifier.js.map +0 -1
- package/dist/lib/validators.d.ts +0 -55
- package/dist/lib/validators.d.ts.map +0 -1
- package/dist/lib/validators.js +0 -166
- package/dist/lib/validators.js.map +0 -1
- package/dist/services/account.d.ts +0 -8
- package/dist/services/account.d.ts.map +0 -1
- package/dist/services/account.js +0 -15
- package/dist/services/account.js.map +0 -1
- package/dist/services/app.d.ts +0 -38
- package/dist/services/app.d.ts.map +0 -1
- package/dist/services/app.js +0 -145
- package/dist/services/app.js.map +0 -1
- package/dist/services/browser-auth.d.ts +0 -15
- package/dist/services/browser-auth.d.ts.map +0 -1
- package/dist/services/browser-auth.js +0 -188
- package/dist/services/browser-auth.js.map +0 -1
- package/dist/services/normalize-app-id.d.ts +0 -12
- package/dist/services/normalize-app-id.d.ts.map +0 -1
- package/dist/services/normalize-app-id.js +0 -38
- package/dist/services/normalize-app-id.js.map +0 -1
- package/dist/services/oauth-metadata.d.ts +0 -7
- package/dist/services/oauth-metadata.d.ts.map +0 -1
- package/dist/services/oauth-metadata.js +0 -43
- package/dist/services/oauth-metadata.js.map +0 -1
- package/dist/services/oauth-refresh.d.ts +0 -14
- package/dist/services/oauth-refresh.d.ts.map +0 -1
- package/dist/services/oauth-refresh.js +0 -70
- package/dist/services/oauth-refresh.js.map +0 -1
- package/dist/services/scopes-html.d.ts +0 -3
- package/dist/services/scopes-html.d.ts.map +0 -1
- package/dist/services/scopes-html.js +0 -516
- package/dist/services/scopes-html.js.map +0 -1
- package/dist/services/scopes-web.d.ts +0 -12
- package/dist/services/scopes-web.d.ts.map +0 -1
- package/dist/services/scopes-web.js +0 -95
- package/dist/services/scopes-web.js.map +0 -1
- package/dist/services/skill.d.ts +0 -48
- package/dist/services/skill.d.ts.map +0 -1
- package/dist/services/skill.js +0 -223
- package/dist/services/skill.js.map +0 -1
- package/dist/skills/index.d.ts +0 -47
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -61
- package/dist/skills/index.js.map +0 -1
- package/dist/templates/files/app-config.json.tmpl +0 -25
- package/dist/templates/index.d.ts +0 -36
- package/dist/templates/index.d.ts.map +0 -1
- package/dist/templates/index.js +0 -105
- package/dist/templates/index.js.map +0 -1
- package/dist/types.d.ts +0 -32
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- /package/dist/{templates → bin}/files/gitignore.tmpl +0 -0
- /package/dist/{templates → bin}/files/src/oauth/package.json.tmpl +0 -0
|
@@ -33,11 +33,21 @@ Implements the authorization code flow to obtain access tokens for the Brevo API
|
|
|
33
33
|
- Other grant types supported by the Brevo OAuth server but not wired into this scaffold: `client_credentials`
|
|
34
34
|
|
|
35
35
|
## OAuth flow
|
|
36
|
+
{{#if private}}
|
|
36
37
|
1. `/auth/login` — generates CSRF state, redirects user to `{OAUTH_BASE}/realms/{realm}/oauth/authorize`
|
|
37
38
|
2. User authorizes -> OAuth server redirects to `/auth/callback` with an auth code
|
|
38
|
-
3. Server validates state, exchanges code for access + refresh tokens via `POST /realms/{realm}/oauth/token`
|
|
39
|
+
3. Server validates state, exchanges code for access + refresh tokens via `POST /realms/{realm}/oauth/token` (authenticated with `client_secret`)
|
|
39
40
|
4. Verifies connection via `GET /realms/{realm}/oauth/userinfo`
|
|
40
41
|
5. `/auth/refresh` — refreshes tokens using `refresh_token` grant
|
|
42
|
+
{{/if}}
|
|
43
|
+
{{#if public}}
|
|
44
|
+
This is a **public app** — the flow uses **PKCE (RFC 7636)** and sends **no `client_secret`**:
|
|
45
|
+
1. `/auth/login` — generates CSRF state + a PKCE `code_verifier`, then redirects user to `{OAUTH_BASE}/realms/{realm}/oauth/authorize` with `code_challenge` (S256) + `code_challenge_method=S256`
|
|
46
|
+
2. User authorizes -> OAuth server redirects to `/auth/callback` with an auth code
|
|
47
|
+
3. Server validates state, exchanges code + `code_verifier` (no `client_secret`) for access + refresh tokens via `POST /realms/{realm}/oauth/token`
|
|
48
|
+
4. Verifies connection via `GET /realms/{realm}/oauth/userinfo`
|
|
49
|
+
5. `/auth/refresh` — refreshes tokens using `refresh_token` grant (no `client_secret`)
|
|
50
|
+
{{/if}}
|
|
41
51
|
|
|
42
52
|
## Development
|
|
43
53
|
```bash
|
|
@@ -47,9 +47,17 @@ brevo app start oauth # port is read from app-config.json; override with
|
|
|
47
47
|
| `GET /realms/{realm}/.well-known/openid-configuration` | OIDC discovery |
|
|
48
48
|
|
|
49
49
|
## Key flows
|
|
50
|
+
{{#if private}}
|
|
50
51
|
- `/auth/login` — generates random CSRF state, redirects to OAuth authorize endpoint
|
|
51
52
|
- `/auth/callback` — validates state, POSTs code to `/realms/{realm}/oauth/token`, stores tokens
|
|
52
53
|
- `/auth/refresh` — refreshes tokens using `refresh_token` grant
|
|
54
|
+
{{/if}}
|
|
55
|
+
{{#if public}}
|
|
56
|
+
This is a **public app**, so the flow uses **PKCE (RFC 7636)** and no client secret:
|
|
57
|
+
- `/auth/login` — generates random CSRF state **and a PKCE `code_verifier`**, then redirects to the OAuth authorize endpoint sending `code_challenge` (S256 hash of the verifier) + `code_challenge_method=S256`. The verifier is stored server-side and never sent to the browser.
|
|
58
|
+
- `/auth/callback` — validates state, POSTs code **plus the `code_verifier`** (no `client_secret`) to `/realms/{realm}/oauth/token`, stores tokens
|
|
59
|
+
- `/auth/refresh` — refreshes tokens using the `refresh_token` grant (no `client_secret`)
|
|
60
|
+
{{/if}}
|
|
53
61
|
|
|
54
62
|
## Next steps
|
|
55
63
|
- Replace the in-memory token store in `src/oauth/token-store.js` with a real database (the global singleton is process-local so data is lost on restart, and it is not session-isolated, so multiple users/sessions/tabs can read or overwrite the same in-memory tokens)
|
|
@@ -55,28 +55,25 @@ Brevo OAuth integration for testing and implementing app authorization flows, sc
|
|
|
55
55
|
|
|
56
56
|
## Updating your app
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
`brevo app upload` has no edit flags — it always pushes the full contents of `app-config.json`. To change something, edit the file, then run:
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
brevo app
|
|
62
|
-
brevo app update --redirect-url https://myapp.com/callback
|
|
63
|
-
brevo app update --name "New Name" --redirect-url https://myapp.com/callback
|
|
64
|
-
brevo app update --logo-uri https://example.com/logo.png
|
|
61
|
+
brevo app upload
|
|
65
62
|
```
|
|
66
63
|
|
|
67
|
-
|
|
64
|
+
For example, to rename the app or add a redirect URL, edit `appName` or `auth.redirectUris` in `app-config.json`:
|
|
68
65
|
|
|
69
|
-
|
|
66
|
+
```bash
|
|
67
|
+
# 1. Edit app-config.json (e.g. change "appName", or append to "auth.redirectUris")
|
|
68
|
+
# 2. Push the change
|
|
69
|
+
brevo app upload
|
|
70
|
+
```
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
- Run `brevo app update` **without flags** to replace the remote configuration with the local file.
|
|
73
|
-
- When flags are provided, the local `app-config.json` is updated automatically after a successful API call.
|
|
72
|
+
`brevo app upload` always fetches the current server state first and shows a diff before pushing — review it before confirming (or pass `--yes` to skip the prompt).
|
|
74
73
|
|
|
75
|
-
|
|
74
|
+
To avoid unintentionally accumulating stale redirect URLs over time, periodically review `auth.redirectUris` in `app-config.json` and remove obsolete entries before running `brevo app upload`.
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
brevo app update --app-id {{APP_ID}} --name "New Name"
|
|
79
|
-
```
|
|
76
|
+
`brevo app upload` has no `--app-id` flag — it always resolves the app from this directory's `app-config.json`. To manage a different app, `cd` into that app's project directory (or use `brevo app credentials --app-id <id>` to inspect it without editing).
|
|
80
77
|
|
|
81
78
|
## Docs
|
|
82
79
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"appId": "{{APP_ID}}",
|
|
3
|
+
"appName": "{{APP_NAME}}",
|
|
4
|
+
"version": "{{APP_VERSION}}",
|
|
5
|
+
"logoUri": "{{LOGO_URI}}",
|
|
6
|
+
"distribution_type": "{{DISTRIBUTION}}",
|
|
7
|
+
{{#if ui_app}}
|
|
8
|
+
"ui_app": {{UI_APP_JSON}},
|
|
9
|
+
{{/if}}
|
|
10
|
+
"auth": {
|
|
11
|
+
{{#if oauth}}
|
|
12
|
+
"scopes": {{SCOPES_JSON}},
|
|
13
|
+
"redirectUris": {{REDIRECT_URLS_JSON}}
|
|
14
|
+
{{/if}}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# Brevo OAuth Credentials — copy this file to .env.local and fill in your values
|
|
2
|
+
{{#if private}}
|
|
2
3
|
# Get CLIENT_ID and CLIENT_SECRET: brevo app credentials --reveal-secret
|
|
4
|
+
{{/if}}
|
|
5
|
+
{{#if public}}
|
|
6
|
+
# Get CLIENT_ID: brevo app credentials
|
|
7
|
+
# This is a public app — it uses PKCE (RFC 7636) and no client secret.
|
|
8
|
+
{{/if}}
|
|
3
9
|
|
|
4
10
|
CLIENT_ID=your_client_id_here
|
|
11
|
+
{{#if private}}
|
|
5
12
|
CLIENT_SECRET=your_client_secret_here
|
|
13
|
+
{{/if}}
|
|
6
14
|
REDIRECT_URI={{REDIRECT_URI}}
|
|
7
15
|
|
|
8
16
|
# OAuth server
|
|
@@ -38,7 +38,9 @@ const REVEAL_SCRIPT =
|
|
|
38
38
|
'<script>document.querySelectorAll(".reveal-btn").forEach(function(btn){btn.addEventListener("click",function(){btn.parentElement.querySelector(".token-value").classList.add("revealed");btn.classList.add("hidden")})});</script>';
|
|
39
39
|
|
|
40
40
|
const CLIENT_ID = process.env.CLIENT_ID;
|
|
41
|
+
{{#if private}}
|
|
41
42
|
const CLIENT_SECRET = process.env.CLIENT_SECRET;
|
|
43
|
+
{{/if}}
|
|
42
44
|
const REDIRECT_URI = process.env.REDIRECT_URI || '{{REDIRECT_URI}}';
|
|
43
45
|
const OAUTH_BASE = process.env.OAUTH_BASE || '{{OAUTH_BASE}}';
|
|
44
46
|
const OAUTH_REALM = process.env.OAUTH_REALM || '{{OAUTH_REALM}}';
|
|
@@ -55,19 +57,48 @@ try {
|
|
|
55
57
|
console.warn('Warning: Could not read app-config.json — defaulting scopes to "all".');
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
// Build the OAuth authorization URL. Extracted so /auth/login and the
|
|
61
|
+
// server.js landing page render the exact same URL — no drift between the two.
|
|
62
|
+
{{#if public}}
|
|
63
|
+
// `codeChallenge` is the PKCE (RFC 7636) S256 challenge. Base64url output is
|
|
64
|
+
// already URL-safe, so it is not re-encoded (matching the `state` treatment).
|
|
65
|
+
function buildAuthorizeUrl({ state, codeChallenge }) {
|
|
66
|
+
{{/if}}
|
|
67
|
+
{{#if private}}
|
|
68
|
+
function buildAuthorizeUrl({ state }) {
|
|
69
|
+
{{/if}}
|
|
70
|
+
let authUrl = `${realmPath}/authorize?` +
|
|
64
71
|
`response_type=code` +
|
|
65
72
|
`&client_id=${encodeURIComponent(CLIENT_ID)}` +
|
|
66
73
|
`&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
|
|
67
74
|
`&state=${state}` +
|
|
68
75
|
`&scope=${encodeURIComponent(SCOPES)}`;
|
|
76
|
+
{{#if public}}
|
|
77
|
+
authUrl += `&code_challenge=${codeChallenge}&code_challenge_method=S256`;
|
|
78
|
+
{{/if}}
|
|
79
|
+
return authUrl;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Step 1: Redirect user to authorization page
|
|
83
|
+
router.get('/login', (req, res) => {
|
|
84
|
+
const crypto = require('crypto');
|
|
85
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
86
|
+
tokenStore.setState(state);
|
|
87
|
+
{{#if public}}
|
|
88
|
+
|
|
89
|
+
// PKCE (RFC 7636): create a high-entropy verifier, keep it server-side, and
|
|
90
|
+
// send only its SHA-256 (S256) hash on the authorize request. The verifier
|
|
91
|
+
// itself is presented only later, on the token exchange in /auth/callback.
|
|
92
|
+
const codeVerifier = crypto.randomBytes(32).toString('base64url');
|
|
93
|
+
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
|
|
94
|
+
tokenStore.setVerifier(codeVerifier);
|
|
95
|
+
|
|
96
|
+
res.redirect(buildAuthorizeUrl({ state, codeChallenge }));
|
|
97
|
+
{{/if}}
|
|
98
|
+
{{#if private}}
|
|
69
99
|
|
|
70
|
-
res.redirect(
|
|
100
|
+
res.redirect(buildAuthorizeUrl({ state }));
|
|
101
|
+
{{/if}}
|
|
71
102
|
});
|
|
72
103
|
|
|
73
104
|
// Step 2: Handle the OAuth callback
|
|
@@ -94,7 +125,14 @@ router.get('/callback', async (req, res) => {
|
|
|
94
125
|
new URLSearchParams({
|
|
95
126
|
grant_type: 'authorization_code',
|
|
96
127
|
client_id: CLIENT_ID,
|
|
128
|
+
{{#if private}}
|
|
97
129
|
client_secret: CLIENT_SECRET,
|
|
130
|
+
{{/if}}
|
|
131
|
+
{{#if public}}
|
|
132
|
+
// PKCE (RFC 7636): prove possession of the verifier generated in /login.
|
|
133
|
+
// Public clients hold no secret, so this replaces client_secret entirely.
|
|
134
|
+
code_verifier: tokenStore.getVerifier(),
|
|
135
|
+
{{/if}}
|
|
98
136
|
code,
|
|
99
137
|
redirect_uri: REDIRECT_URI,
|
|
100
138
|
}),
|
|
@@ -165,7 +203,9 @@ router.get('/refresh', async (req, res) => {
|
|
|
165
203
|
new URLSearchParams({
|
|
166
204
|
grant_type: 'refresh_token',
|
|
167
205
|
client_id: CLIENT_ID,
|
|
206
|
+
{{#if private}}
|
|
168
207
|
client_secret: CLIENT_SECRET,
|
|
208
|
+
{{/if}}
|
|
169
209
|
refresh_token: refreshToken,
|
|
170
210
|
}),
|
|
171
211
|
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
|
|
@@ -204,3 +244,5 @@ router.get('/refresh', async (req, res) => {
|
|
|
204
244
|
});
|
|
205
245
|
|
|
206
246
|
module.exports = router;
|
|
247
|
+
// Exposed so server.js renders the same authorize URL the /login redirect uses.
|
|
248
|
+
module.exports.buildAuthorizeUrl = buildAuthorizeUrl;
|
|
@@ -14,11 +14,21 @@ if (!fs.existsSync(envPath)) {
|
|
|
14
14
|
|
|
15
15
|
require('dotenv').config({ path: envPath });
|
|
16
16
|
|
|
17
|
+
{{#if private}}
|
|
17
18
|
if (!process.env.CLIENT_ID || !process.env.CLIENT_SECRET) {
|
|
18
19
|
console.error('\nError: CLIENT_ID and CLIENT_SECRET must be set in .env.local');
|
|
19
20
|
console.error('Get your credentials with: brevo app credentials --reveal-secret\n');
|
|
20
21
|
process.exit(1);
|
|
21
22
|
}
|
|
23
|
+
{{/if}}
|
|
24
|
+
{{#if public}}
|
|
25
|
+
// Public apps use PKCE (RFC 7636) — no client secret is involved.
|
|
26
|
+
if (!process.env.CLIENT_ID) {
|
|
27
|
+
console.error('\nError: CLIENT_ID must be set in .env.local');
|
|
28
|
+
console.error('Get your credentials with: brevo app credentials\n');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
{{/if}}
|
|
22
32
|
|
|
23
33
|
if (!process.env.OAUTH_BASE || process.env.OAUTH_BASE.startsWith('{{')) {
|
|
24
34
|
console.error('\nError: OAUTH_BASE is not set or contains a placeholder.');
|
|
@@ -33,24 +43,20 @@ if (!process.env.OAUTH_REALM || process.env.OAUTH_REALM.startsWith('{{')) {
|
|
|
33
43
|
|
|
34
44
|
const express = require('express');
|
|
35
45
|
const authRouter = require('./handler');
|
|
46
|
+
const { buildAuthorizeUrl } = authRouter;
|
|
36
47
|
|
|
37
48
|
const app = express();
|
|
38
49
|
const PORT = process.env.PORT || 3009;
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
+ `?response_type=code`
|
|
50
|
-
+ `&client_id=${encodeURIComponent(process.env.CLIENT_ID)}`
|
|
51
|
-
+ `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`
|
|
52
|
-
+ `&state=[random]`
|
|
53
|
-
+ `&scope=${encodeURIComponent(SCOPES)}`;
|
|
51
|
+
// Build the displayed authorization URL with the same helper /auth/login uses,
|
|
52
|
+
// so what the landing page shows always matches what the redirect actually
|
|
53
|
+
// sends. Placeholders (`[random]` state) stand in for the per-request values.
|
|
54
|
+
{{#if private}}
|
|
55
|
+
const AUTH_URL = buildAuthorizeUrl({ state: '[random]' });
|
|
56
|
+
{{/if}}
|
|
57
|
+
{{#if public}}
|
|
58
|
+
const AUTH_URL = buildAuthorizeUrl({ state: '[random]', codeChallenge: '[generated-per-login]' });
|
|
59
|
+
{{/if}}
|
|
54
60
|
|
|
55
61
|
app.use('/auth', authRouter);
|
|
56
62
|
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
let tokens = {};
|
|
4
4
|
let csrfState = null;
|
|
5
|
+
{{#if public}}
|
|
6
|
+
let codeVerifier = null;
|
|
7
|
+
{{/if}}
|
|
5
8
|
|
|
6
9
|
module.exports = {
|
|
7
10
|
saveTokens({ access_token, refresh_token, expires_in }) {
|
|
@@ -31,9 +34,25 @@ module.exports = {
|
|
|
31
34
|
getState() {
|
|
32
35
|
return csrfState;
|
|
33
36
|
},
|
|
37
|
+
{{#if public}}
|
|
38
|
+
|
|
39
|
+
// PKCE (RFC 7636): stash the code_verifier between /auth/login and
|
|
40
|
+
// /auth/callback. Same process-local, non-session-isolated limitation as the
|
|
41
|
+
// token store above — replace with your database in production.
|
|
42
|
+
setVerifier(verifier) {
|
|
43
|
+
codeVerifier = verifier;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
getVerifier() {
|
|
47
|
+
return codeVerifier;
|
|
48
|
+
},
|
|
49
|
+
{{/if}}
|
|
34
50
|
|
|
35
51
|
clear() {
|
|
36
52
|
tokens = {};
|
|
37
53
|
csrfState = null;
|
|
54
|
+
{{#if public}}
|
|
55
|
+
codeVerifier = null;
|
|
56
|
+
{{/if}}
|
|
38
57
|
},
|
|
39
58
|
};
|