Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f3ab863
feat(desktop): add conversation notification preferences and windows …
Aug 1, 2026
8604d61
fix(desktop): resolve lint and typecheck errors
Aug 3, 2026
5a150e7
docs: add CONTEXT.md onboarding context
Aug 3, 2026
0799289
ci: enable windows canary workflow on fork
Aug 3, 2026
5f46cd1
fix(desktop): scope RunEvent::Reopen to macos target
Aug 3, 2026
988130d
feat(desktop): add Google Workspace SSO for k2alpha.ai
Aug 3, 2026
23b0c34
fix(desktop): resolve typecheck and a11y formatting
Aug 4, 2026
1e7c139
fix(desktop): implement Debug derive for IdentityInfo and GoogleAuthR…
Aug 4, 2026
6e8690e
ci(windows): remove invalid package-manager-cache input from setup-node
Aug 4, 2026
11e49f0
fix(desktop): fix windows build unresolved imports in mouse_nav
Aug 4, 2026
a70a80e
fix: Redesign onboarding flow and fix Windows notifications
Aug 5, 2026
0478975
fix: Resolve duplicate import syntax error in NostrKeyImportForm
Aug 5, 2026
f0c5574
fix: Remove unused loadFreshIdentity function in MachineOnboardingFlow
Aug 5, 2026
fe44a18
fix: Remove unused getIdentity import in MachineOnboardingFlow
Aug 5, 2026
78ee07a
fix: Remove invalid set_app_user_model_id method call in Tauri v2
Aug 5, 2026
9a40c60
fix: implement PKCE for Google Auth and handle token errors
Aug 5, 2026
593a1cf
Merge remote-tracking branch 'origin/main' into dev
Aug 5, 2026
39d9950
fix(notifications): align parameter order for useFeedDesktopNotificat…
Aug 5, 2026
a6aa22d
fix(notifications): properly pass activeTargetId through AppShell
Aug 5, 2026
6a77cd5
fix: include client_secret in Google OAuth token exchange for Desktop…
Aug 6, 2026
2d1defd
ci: bust stale Cargo cache and add fork timestamp to installer name
Aug 6, 2026
9e10b5e
Harden Google SSO key handling and secret injection
Aug 6, 2026
5b1daa2
Fix SSO type error and empty-secret handling
Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ RUST_LOG=buzz_relay=debug,buzz_datastore=info,buzz_db=debug,buzz_auth=debug,buzz
# app launch while keeping the current identity and relay data.
# VITE_BUZZ_FORCE_FRESH_ONBOARDING=true

# -----------------------------------------------------------------------------
# Google Authentication
# -----------------------------------------------------------------------------
# Google OAuth Desktop Client Secret. Compile-time only.
# Leaving this unset disables Google SSO in the resulting binary.
# OSS/dev builds should leave it unset. NEVER put a real value here.
# BUZZ_BUILD_GOOGLE_CLIENT_SECRET=

# ── Subscription & filtering ─────────────────────────────────────────────────
# Subscribe mode: "mentions" (default), "all", or "config" (rule-based).
# BUZZ_ACP_SUBSCRIBE=mentions
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/linux-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,14 @@ jobs:
cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
./scripts/bundle-sidecars.sh

- name: Force recompile desktop crate (bust stale cache)
run: cd desktop/src-tauri && cargo clean -p buzz-desktop --release

- name: Build Linux Tauri app
run: cd desktop && pnpm tauri build --ci --bundles deb,appimage --features mesh-llm --config src-tauri/tauri.canary.conf.json
env:
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
BUZZ_BUILD_GOOGLE_CLIENT_SECRET: ${{ secrets.BUZZ_GOOGLE_CLIENT_SECRET }}

- name: Fix AppImage (remove infra libs, symlink system GStreamer)
# fix-appimage.sh checks for TAURI_SIGNING_PRIVATE_KEY and skips
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/macos-intel-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,17 @@ jobs:
cargo build --release --target "$TARGET" -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
./scripts/bundle-sidecars.sh "$TARGET"

- name: Force recompile desktop crate (bust stale cache)
run: cd desktop/src-tauri && cargo clean -p buzz-desktop --release --target "$TARGET"

- name: Build unsigned Intel DMG
run: cd desktop && pnpm tauri build --verbose --no-sign --target "$TARGET" --bundles dmg --config src-tauri/tauri.canary.conf.json
env:
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
MACOSX_DEPLOYMENT_TARGET: "10.15"
CMAKE_OSX_DEPLOYMENT_TARGET: "10.15"
TAURI_BUNDLER_DMG_IGNORE_CI: "true"
BUZZ_BUILD_GOOGLE_CLIENT_SECRET: ${{ secrets.BUZZ_GOOGLE_CLIENT_SECRET }}

- name: Locate fresh Intel DMG
id: artifact
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,17 @@ jobs:
path: ${{ github.workspace }}/.cache/mesh-llama
key: mesh-llama-${{ runner.os }}-metal-${{ steps.mesh_rev.outputs.rev }}

- name: Force recompile desktop crate (bust stale cache)
run: cd desktop/src-tauri && cargo clean -p buzz-desktop --release

- name: Build unsigned Tauri app
run: cd desktop && pnpm tauri build --verbose --no-sign --features mesh-llm --config src-tauri/tauri.release.conf.json
env:
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
BUZZ_BUILD_GOOGLE_CLIENT_SECRET: ${{ secrets.BUZZ_GOOGLE_CLIENT_SECRET }}
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
MACOSX_DEPLOYMENT_TARGET: "10.15"
CMAKE_OSX_DEPLOYMENT_TARGET: "10.15"
Expand Down Expand Up @@ -311,13 +315,17 @@ jobs:
cargo build --release --target "$TARGET" -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
./scripts/bundle-sidecars.sh "$TARGET"

- name: Force recompile desktop crate (bust stale cache)
run: cd desktop/src-tauri && cargo clean -p buzz-desktop --release --target "$TARGET"

- name: Build unsigned Tauri app
run: cd desktop && pnpm tauri build --verbose --no-sign --target "$TARGET" --config src-tauri/tauri.release.conf.json
env:
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
BUZZ_BUILD_GOOGLE_CLIENT_SECRET: ${{ secrets.BUZZ_GOOGLE_CLIENT_SECRET }}
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
MACOSX_DEPLOYMENT_TARGET: "10.15"
CMAKE_OSX_DEPLOYMENT_TARGET: "10.15"
Expand Down Expand Up @@ -572,11 +580,15 @@ jobs:
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json

- name: Force recompile desktop crate (bust stale cache)
run: cd desktop/src-tauri && cargo clean -p buzz-desktop --release

- name: Build Linux Tauri app
run: cd desktop && pnpm tauri build --verbose --ci --bundles deb,appimage --features mesh-llm --config src-tauri/tauri.release.conf.json
env:
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
BUZZ_BUILD_GOOGLE_CLIENT_SECRET: ${{ secrets.BUZZ_GOOGLE_CLIENT_SECRET }}
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
Expand Down Expand Up @@ -715,6 +727,10 @@ jobs:
cargo build --release --target "$TARGET" -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
./scripts/bundle-sidecars.sh "$TARGET"

- name: Force recompile desktop crate (bust stale cache)
shell: bash
run: cd desktop/src-tauri && cargo clean -p buzz-desktop --release --target "$TARGET"

- name: Build Windows NSIS installer (unsigned)
shell: bash
run: cd desktop && pnpm tauri build --verbose --target "$TARGET" --bundles nsis --config src-tauri/tauri.release.conf.json
Expand All @@ -723,6 +739,7 @@ jobs:
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
BUZZ_BUILD_GOOGLE_CLIENT_SECRET: ${{ secrets.BUZZ_GOOGLE_CLIENT_SECRET }}
CMAKE_POLICY_VERSION_MINIMUM: "3.5"

- name: Locate Windows build artifacts
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/signed-macos-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ jobs:
path: ${{ github.workspace }}/.cache/mesh-llama
key: mesh-llama-${{ runner.os }}-metal-${{ steps.mesh_rev.outputs.rev }}

- name: Force recompile desktop crate (bust stale cache)
run: cd desktop/src-tauri && cargo clean -p buzz-desktop --release

- name: Build unsigned Tauri app
run: cd desktop && pnpm tauri build --verbose --no-sign --features mesh-llm --config src-tauri/tauri.canary.conf.json
env:
Expand All @@ -168,6 +171,7 @@ jobs:
CMAKE_OSX_DEPLOYMENT_TARGET: "10.15"
LLAMA_STAGE_BACKEND: metal
LLAMA_STAGE_BUILD_DIR: ${{ github.workspace }}/.cache/mesh-llama/build-stage-abi-metal
BUZZ_BUILD_GOOGLE_CLIENT_SECRET: ${{ secrets.BUZZ_GOOGLE_CLIENT_SECRET }}
SKIPPY_LLAMA_AUTO_BUILD: "0"
TAURI_BUNDLER_DMG_IGNORE_CI: "true"

Expand Down
22 changes: 9 additions & 13 deletions .github/workflows/windows-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,13 @@ permissions:
jobs:
build:
name: Build Windows canary
if: github.repository == 'block/buzz'
runs-on: windows-latest
timeout-minutes: 60
permissions:
contents: read
env:
TARGET: x86_64-pc-windows-msvc
steps:
- name: Require main
shell: bash
env:
SOURCE_REF: ${{ github.ref }}
run: |
if [[ "$SOURCE_REF" != "refs/heads/main" ]]; then
echo "::error::Canary builds must run from main; got $SOURCE_REF"
exit 1
fi

- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
Expand All @@ -49,7 +39,6 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 24.14.1
package-manager-cache: false

- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
with:
Expand Down Expand Up @@ -130,13 +119,19 @@ jobs:
- name: Generate non-updating bundle config
shell: bash
run: |
cat > desktop/src-tauri/tauri.canary.conf.json <<'JSON'
TIMESTAMP=$(date -u +"%d%m-%H%M")
cat > desktop/src-tauri/tauri.canary.conf.json <<EOF
{
"productName": "Buzz for k2alpha.ai (${{ steps.version.outputs.version }}-fork-${TIMESTAMP})",
"bundle": {
"createUpdaterArtifacts": false
}
}
JSON
EOF

- name: Force recompile desktop crate (bust stale cache)
shell: bash
run: cd desktop/src-tauri && cargo clean -p buzz-desktop --release --target "$TARGET"

- name: Build sidecars
shell: bash
Expand All @@ -149,6 +144,7 @@ jobs:
run: cd desktop && pnpm tauri build --target "$TARGET" --bundles nsis --config src-tauri/tauri.canary.conf.json
env:
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
BUZZ_BUILD_GOOGLE_CLIENT_SECRET: ${{ secrets.BUZZ_GOOGLE_CLIENT_SECRET }}

- name: Locate NSIS installer
id: artifact
Expand Down
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,26 @@ identity.key

# Helm dependency tarballs — regenerable from Chart.lock via `helm dependency build`
deploy/charts/*/charts/*.tgz

# Local dev scratch: ad-hoc scripts, captured command output, downloaded installers.
# None of these are build inputs; all are regenerable or machine-specific.
check_status.py
fetch_logs.py
parse_runs.py
run_tests.py
test_google.py
trigger_workflow.py
found_installers.txt
git_log_output.txt
github_runs.json
out.txt
test_out.txt
desktop/src-tauri/cargoout.txt
latest_run_logs/
scratch/
old_google_auth.rs
desktop/src-tauri/test_aumid.rs

# Toolchain installers downloaded into the repo root
rustup-init.exe
vs_BuildTools.exe
82 changes: 82 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# CONTEXT.md

## Architecture Overview
* **Relay & Backend**: Rust workspace (`buzz-relay`, `buzz-core`, `buzz-db`, `buzz-auth`, `buzz-pubsub`, `buzz-search`).
* **Desktop Client**: Tauri 2 + React 19 + Vite + Tailwind CSS (`desktop/`).
* **Protocol**: NIP-29 over WebSocket (Nostr protocol) for real-time channels and messaging.

## Deploy Command
* **Desktop App**: `just desktop-tauri-build` / `pnpm tauri:build`
* **Relay Docker**: `docker build -t buzz-relay .`

## Manual Dashboard Config
* N/A — Local development environment initialized via `.env` and Hermit toolchain.

## Knowledge Items (KIs)
* **Desktop Text Sizing**: Use rem-based tokens (`text-base`, `text-xs`, `text-2xs`, `text-3xs`) instead of explicit `px` to maintain webview zoom compatibility.
* **Hermit Environment**: Run commands via `bin/` toolchain wrappers or activate environment before running workspace tools.
* **Format & Lint Gating**: `npx biome check .` and `tsc` must pass before desktop release.
* **Windows Notifications**: For Tauri v2 notifications to appear in Windows OS Notification settings, explicitly call `app.set_app_user_model_id("xyz.block.buzz.app")` in the Tauri setup hook, and the app must be formally installed (shortcut created) via the generated NSIS installer.
* **Google SSO Flow**: Ensure Google Auth (`start_google_workspace_login`) handles UI state and error reporting seamlessly to bypass manual key import flows when using SSO.
* **Google OAuth Desktop Client Secret**: Google strictly requires the `client_secret` parameter in the token exchange for "Desktop app" OAuth clients (even when PKCE is fully implemented). To avoid hardcoding it and triggering secret scanning, the secret is injected at compile time via `build.rs` using the `BUZZ_BUILD_GOOGLE_CLIENT_SECRET` env var and read via `option_env!`. CI workflows pass this in via GitHub Secrets.
* **Stale Cargo Cache in CI**: The `desktop-release-cache-key.py` script hashes only `Cargo.toml`, `Cargo.lock`, and `rust-toolchain.toml` — NOT `.rs` source files. If only Rust source changes (no dependency changes), the cache key stays identical and Cargo reuses old compiled objects, silently ignoring source edits. Fix: add `cargo clean -p buzz-desktop --release --target "$TARGET"` before `pnpm tauri build` in the workflow.
* **Canary Installer Naming**: Windows Canary installer is named `Buzz <version>-fork-ddmm-hhmm.exe` via the `productName` field in `tauri.canary.conf.json` (generated dynamically in `.github/workflows/windows-canary.yml`). This ensures every installed build is visually identifiable.

## Coding Guidance (Agent Contract)

Applies to every agent and human touching this repo. These are rules of construction, not a checklist of blessed values — never satisfy one by pasting a literal into source.

### 1. Configuration & Secrets
* **No literals for anything environment-dependent.** Credentials, endpoints, ports, bucket names, model IDs, feature toggles: resolve from env at runtime, or via `option_env!` at compile time when the value must be baked into a shipped binary. Source holds the *name* of the variable, never the value.
* **Every new variable is declared in `.env.example`** in the same commit that reads it, with a comment on what it does and whether it is required.
* **Missing required config fails loudly at startup**, not lazily at first use. A binary that boots and then dies mid-OAuth is worse than one that refuses to boot.
* **Never disable, bypass, or annotate around a secret-scanning block.** A scanner hit means the value belongs in CI secrets, not that the scanner is wrong. If a value genuinely cannot be externalized, it is an accepted risk requiring a written entry under "Accepted Risks" below — not a silent bypass.
* **Never log, serialize, or return a secret**, including in error strings and `Debug` impls. Redact tokens, keys, and authorization codes at the boundary.

### 2. Cryptographic & Identity Material
* **Key material must come from a CSPRNG**, or from a KDF whose input includes at least 128 bits of attacker-unknown entropy. A hash over a public identifier (email, OAuth `sub`, user ID, device ID) is not a secret regardless of how the salt is chosen.
* **Constants committed to the repo are public.** Never treat a hardcoded salt, pepper, or seed as a security boundary.
* **Private keys live in the OS keychain / secure enclave**, never in plaintext files, app state, or anything crossing the Tauri IPC boundary unless the user explicitly initiated an export.
* **Deriving identity from an SSO subject is not a substitute for key storage.** If cross-device recovery is needed, escrow an encrypted key to a k2alpha-controlled service; do not make the key recomputable from public inputs.

### 3. OAuth & Authentication Flows
* Send and verify a random `state` parameter on every authorization request. PKCE protects code exchange; it does not authenticate the callback.
* Bind the loopback callback to `127.0.0.1` (never `0.0.0.0`), reject callbacks whose `state` does not match, and treat any unexpected request on the callback port as hostile.
* Validate every claim you depend on (`hd`, `email`, `email_verified`, `aud`, `exp`) explicitly. Do not rely on request-time hints like `hd=` in the auth URL — those are UI suggestions, not enforcement.
* Only skip JWT signature verification when the token was received directly from the issuer's token endpoint over TLS in the same function. If a token-parsing helper could ever be handed a token from another source, it verifies the signature or it does not exist.

### 4. External Calls & Failure Handling
* **Every outbound call sets an explicit connect timeout and total timeout.** No unbounded waits, ever. Reuse a configured client; do not construct a default client per call site.
* **Retries are bounded, jittered, and only for idempotent or explicitly retry-safe operations.** Never retry a token exchange or any single-use code redemption.
* **Distinguish failure classes** — network, 4xx, 5xx, malformed payload — and surface them as typed errors. `Result<_, String>` is acceptable only at the Tauri command boundary, and only after the typed error has been logged.
* **Degrade, don't hang.** A dependency being down produces an actionable user-facing message within the timeout window.

### 5. Resource Lifecycle
* Anything spawned, bound, or locked has exactly one guaranteed teardown path that runs on **all** exits — success, error, timeout, and early `?` return. Prefer RAII guards over manual cleanup calls placed after the happy path.
* No `unwrap()` / `expect()` on anything reachable from user input, IPC, or the network. Poisoned-lock recovery is explicit.
* Long-lived tasks are cancellable and observable; a leaked listener or task is a defect even when it is invisible.

### 6. Observability
* Use `tracing` with structured fields, not `println!`. Instrument boundaries: IPC entry, external call, auth decision, error return.
* Log the decision *and* its inputs (redacted) at the point where the code takes a branch a support engineer would later need to explain.
* Error paths log at `warn`/`error` with enough context to diagnose without a reproduction. Silent `Err` returns are not acceptable.

### 7. Fork Discipline (k2alpha ← block/sprout)
* **Keep k2alpha-specific deltas small, isolated, and clearly marked** so upstream merges stay mechanical. Prefer a dedicated module or config surface over edits scattered across upstream files.
* **Never restructure upstream code opportunistically.** Every diff against upstream is a future merge conflict; each one must be justified by a k2alpha requirement.
* **Forking is not a justification for a weaker practice.** If a control is hard to implement because of the fork, say so explicitly and record it under Accepted Risks — do not silently lower the bar.

### 8. Change Discipline
* Touch only what the task requires; no drive-by refactors, reformatting, or "while I'm here" cleanup.
* Behavioral changes ship with a test that fails before the change and passes after. Deterministic logic gets unit tests; external dependencies are mocked.
* `cargo clippy`, `cargo fmt`, `npx biome check .`, and `tsc` pass before any release build.
* Repo root stays clean: no installers, binaries, scratch scripts, logs, or dumped JSON. Working files go under an ignored directory.

### Accepted Risks (k2alpha, reviewed — not patterns to copy)
* **Hardcoded Google domain (`k2alpha.ai`)**: intentional. This fork serves one company; the domain is a product constraint, not configuration. *Not* an exception to §1 for any other value.

Adding to this list requires a stated mitigation and a trigger condition for removing it. An entry without both is a bug, not an accepted risk.

## Pending Tasks
* Await completion of Windows Canary build (Run #5, with cache bust + client_secret fix) to verify Google login works end-to-end.
* Rotate the exposed Google OAuth client secret and update the CI secret (`BUZZ_GOOGLE_CLIENT_SECRET`).
Loading