From b706db6e8b06c6f91e90c2823d693e5e0c517474 Mon Sep 17 00:00:00 2001
From: sunshinexcode <24xinhui@163.com>
Date: Tue, 7 Jul 2026 14:52:43 +0800
Subject: [PATCH 1/3] feat(auth): add branded OAuth callback pages
---
internal/cli/auth.go | 20 +++--
internal/cli/oauth_pages.go | 149 +++++++++++++++++++++++++++++++
internal/cli/oauth_pages_test.go | 70 +++++++++++++++
3 files changed, 234 insertions(+), 5 deletions(-)
create mode 100644 internal/cli/oauth_pages.go
create mode 100644 internal/cli/oauth_pages_test.go
diff --git a/internal/cli/auth.go b/internal/cli/auth.go
index 46bae71..8e8863d 100644
--- a/internal/cli/auth.go
+++ b/internal/cli/auth.go
@@ -53,7 +53,9 @@ func (a *App) login(noBrowser bool, region string, progress progressEmitter) (ma
}
timeout = time.Duration(parsed) * time.Millisecond
}
- callback, err := waitForOAuthCallback(state, timeout)
+ callback, err := waitForOAuthCallbackWithPage(state, timeout, callbackPageConfig{
+ Region: loginRegion,
+ })
if err != nil {
return nil, err
}
@@ -284,6 +286,10 @@ type callbackPayload struct {
}
func waitForOAuthCallback(expectedState string, timeout time.Duration) (*callbackServer, error) {
+ return waitForOAuthCallbackWithPage(expectedState, timeout, callbackPageConfig{})
+}
+
+func waitForOAuthCallbackWithPage(expectedState string, timeout time.Duration, page callbackPageConfig) (*callbackServer, error) {
wait := make(chan callbackPayload, 1)
errs := make(chan error, 1)
mux := http.NewServeMux()
@@ -313,18 +319,22 @@ func waitForOAuthCallback(expectedState string, timeout time.Duration) (*callbac
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
oauthErr := r.URL.Query().Get("error")
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
switch {
case oauthErr != "":
- http.Error(w, "Agora CLI login failed. Return to the terminal for details.", http.StatusBadRequest)
+ w.WriteHeader(http.StatusBadRequest)
+ _, _ = io.WriteString(w, renderOAuthCallbackErrorPage(page, "Agora CLI login failed", "Return to the terminal for details."))
errs <- fmt.Errorf("OAuth authorization failed: %s", oauthErr)
case code == "" || state == "":
- http.Error(w, "Agora CLI login callback was missing required fields.", http.StatusBadRequest)
+ w.WriteHeader(http.StatusBadRequest)
+ _, _ = io.WriteString(w, renderOAuthCallbackErrorPage(page, "Missing callback fields", "The OAuth callback did not include the required code and state fields."))
case state != expectedState:
- http.Error(w, "Agora CLI login state mismatch.", http.StatusBadRequest)
+ w.WriteHeader(http.StatusBadRequest)
+ _, _ = io.WriteString(w, renderOAuthCallbackErrorPage(page, "Login state mismatch", "Return to the terminal and restart login to protect this session."))
errs <- errors.New("OAuth state mismatch.")
default:
w.WriteHeader(http.StatusOK)
- _, _ = io.WriteString(w, `
Agora CLI Login CompleteAgora CLI login complete
You can close this browser window and return to your terminal.
`)
+ _, _ = io.WriteString(w, renderOAuthCallbackSuccessPage(page))
wait <- callbackPayload{Code: code, State: state}
}
})
diff --git a/internal/cli/oauth_pages.go b/internal/cli/oauth_pages.go
new file mode 100644
index 0000000..ca405af
--- /dev/null
+++ b/internal/cli/oauth_pages.go
@@ -0,0 +1,149 @@
+package cli
+
+import (
+ "html"
+ "strings"
+)
+
+// callbackPageConfig carries the login region used to brand the browser callback page.
+type callbackPageConfig struct {
+ Region string
+}
+
+// loginPageContent contains the region-specific copy rendered on the OAuth callback page.
+type loginPageContent struct {
+ PageClass string
+ Title string
+ Message string
+ ActionLabel string
+ PrimaryAction string
+ Safety string
+}
+
+// renderOAuthCallbackSuccessPage renders the browser page shown after a successful OAuth callback.
+func renderOAuthCallbackSuccessPage(config callbackPageConfig) string {
+ content := loginPageContentForRegion(config.Region)
+ return renderOAuthCallbackPage(content, false, "", "")
+}
+
+// renderOAuthCallbackErrorPage renders a branded browser page for OAuth callback errors.
+func renderOAuthCallbackErrorPage(config callbackPageConfig, title, message string) string {
+ content := loginPageContentForRegion(config.Region)
+ return renderOAuthCallbackPage(content, true, title, message)
+}
+
+// loginPageContentForRegion returns the final login-page copy for the active control-plane region.
+func loginPageContentForRegion(region string) loginPageContent {
+ if normalizeContextRegion(region) == regionCN {
+ return loginPageContent{
+ PageClass: "shengwang-page",
+ Title: "你已成功登录声网 CLI",
+ Message: "此浏览器步骤已完成。CLI 现在可以将此账号作为当前本地配置继续使用。",
+ ActionLabel: "回到终端后确认当前登录状态。",
+ PrimaryAction: "agora auth status",
+ Safety: "你可以关闭此页面,回到终端继续操作。",
+ }
+ }
+
+ return loginPageContent{
+ PageClass: "agora-page",
+ Title: "You are now authenticated with Agora CLI",
+ Message: "This browser step completed successfully. The CLI can now use this account as your active local configuration.",
+ ActionLabel: "Return to your terminal and confirm the active account.",
+ PrimaryAction: "agora auth status",
+ Safety: "You can close this window and return to your terminal.",
+ }
+}
+
+// renderOAuthCallbackPage builds the complete OAuth callback HTML document.
+func renderOAuthCallbackPage(content loginPageContent, isError bool, errorTitle, errorMessage string) string {
+ title := content.Title
+ message := content.Message
+ if isError {
+ title = valueOrDefault(errorTitle, "Login could not be completed")
+ message = valueOrDefault(errorMessage, "Return to your terminal for details.")
+ }
+
+ var b strings.Builder
+ b.WriteString(`Agora CLI Login`)
+ b.WriteString(brandLogoHTML(content.PageClass))
+ b.WriteString(`CLI
`)
+ b.WriteString(escapeText(title))
+ b.WriteString(`
`)
+ b.WriteString(escapeText(message))
+ b.WriteString(`
`)
+ if !isError {
+ b.WriteString(``)
+ b.WriteString(escapeText(content.ActionLabel))
+ b.WriteString(`
`)
+ b.WriteString(escapeText(content.PrimaryAction))
+ b.WriteString(` `)
+ }
+ b.WriteString(``)
+ b.WriteString(escapeText(content.Safety))
+ b.WriteString(`
`)
+
+ return b.String()
+}
+
+// loginPageCSS returns the shared CSS for the final global and China login pages.
+func loginPageCSS() string {
+ return `
+*{box-sizing:border-box}
+body{margin:0;min-height:100vh;font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
+.page{min-height:100vh;display:grid;place-items:center;padding:90px 28px 48px;position:relative;overflow:hidden;color:var(--ink);background:var(--bg)}
+.page:before{content:"";position:absolute;inset:0;pointer-events:none}
+.card{width:min(900px,100%);position:relative;z-index:2;min-height:auto;padding:42px;border:1px solid var(--line);border-radius:30px;background:var(--panel);box-shadow:var(--shadow);backdrop-filter:blur(18px)}
+.brand{display:inline-flex;align-items:center;gap:10px;color:var(--ink);font-size:20px;font-weight:950;letter-spacing:-.02em}
+.brand-logo{display:inline-flex;align-items:center;flex:0 0 auto}
+.brand-logo svg{display:block;width:100%;height:100%}
+.brand-logo img{display:block;width:100%;height:auto}
+.brand-logo-agora{width:86px;max-height:34px}
+.brand-logo-cn{justify-content:center;width:58px;height:58px;border-radius:16px;color:#fff;background:radial-gradient(circle at 22% 18%,rgba(255,255,255,.32),transparent 26%),linear-gradient(135deg,#1469ff 0%,#0f8fff 48%,#16c7b7 100%);box-shadow:0 16px 36px rgba(20,105,255,.22)}
+.brand-logo-cn svg{width:42px;height:auto}
+.hero{margin-top:54px;max-width:880px}
+h1{margin:0;max-width:820px;font-size:clamp(34px,3.8vw,54px);line-height:1.12;letter-spacing:-.045em}
+.message{max-width:780px;margin:24px 0 0;color:var(--muted);font-size:clamp(15px,1.3vw,18px);line-height:1.7}
+.next{display:grid;grid-template-columns:1fr auto;gap:18px;align-items:center;margin-top:34px;padding:15px 18px;border:1px solid var(--next-line);border-radius:18px;background:var(--next-bg);box-shadow:var(--next-shadow)}
+.next p{margin:0;color:var(--next-text);font-size:clamp(14px,1.25vw,16px);font-weight:650;letter-spacing:-.02em}
+code{padding:0;border-radius:0;background:transparent;color:var(--primary);font:850 clamp(14px,1.25vw,16px) ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap}
+.safety{margin:24px 0 0;color:var(--muted);font-size:14px;line-height:1.65}
+.is-error .next{display:none}
+.agora-page{--ink:#f8fbff;--muted:#a9b8cd;--primary:#35d6a5;--line:rgba(255,255,255,.14);--panel:rgba(9,20,38,.54);--shadow:0 24px 90px rgba(0,0,0,.22);--next-line:rgba(255,255,255,.1);--next-bg:rgba(5,11,20,.72);--next-text:#dbe7ff;--next-shadow:none;--bg:radial-gradient(circle at 12% 12%,rgba(53,214,165,.28),transparent 30%),radial-gradient(circle at 88% 16%,rgba(122,167,255,.22),transparent 32%),radial-gradient(circle at 54% 100%,rgba(62,84,255,.18),transparent 36%),linear-gradient(150deg,#050b14 0%,#101b33 48%,#07111f 100%)}
+.agora-page:before{opacity:.2;background-image:linear-gradient(rgba(255,255,255,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.08) 1px,transparent 1px);background-size:44px 44px;mask-image:radial-gradient(circle at 50% 48%,black,transparent 72%)}
+.shengwang-page{--ink:#10233f;--muted:#5f6f85;--primary:#1469ff;--line:rgba(24,56,96,.14);--panel:linear-gradient(135deg,rgba(255,255,255,.96),rgba(255,255,255,.78)),radial-gradient(circle at 0% 0%,rgba(20,105,255,.08),transparent 34%);--shadow:0 30px 100px rgba(16,35,63,.16),0 1px 0 rgba(255,255,255,.78) inset;--next-line:rgba(24,56,96,.16);--next-bg:#f8fafc;--next-text:#5f6f85;--next-shadow:0 12px 40px rgba(20,61,112,.06);--bg:radial-gradient(circle at 18% 12%,rgba(20,105,255,.34),transparent 28%),radial-gradient(circle at 85% 78%,rgba(22,199,183,.24),transparent 30%),linear-gradient(135deg,#e8f1ff 0%,#f8fbff 42%,#e8fff9 100%)}
+.shengwang-page:before{background:linear-gradient(rgba(10,24,48,.045) 1px,transparent 1px),linear-gradient(90deg,rgba(10,24,48,.045) 1px,transparent 1px),radial-gradient(circle at 50% 0%,rgba(5,8,18,.1),transparent 34%);background-size:44px 44px,44px 44px,auto;opacity:.8;mask-image:radial-gradient(circle at 50% 48%,black,transparent 78%)}
+@media (max-width:900px){
+ .page{padding-top:72px}
+ .card{padding:28px}
+ .next{grid-template-columns:1fr}
+ code{white-space:normal;word-break:break-word}
+}
+`
+}
+
+// brandLogoHTML returns the official region-specific brand mark used in the callback page.
+func brandLogoHTML(pageClass string) string {
+ if pageClass == "shengwang-page" {
+ return ``
+ }
+
+ return `
`
+}
+
+// escapeText escapes user-visible text before it is inserted into HTML.
+func escapeText(value string) string {
+ return html.EscapeString(value)
+}
+
+// escapeAttr escapes attribute values before they are inserted into HTML.
+func escapeAttr(value string) string {
+ return html.EscapeString(value)
+}
diff --git a/internal/cli/oauth_pages_test.go b/internal/cli/oauth_pages_test.go
new file mode 100644
index 0000000..d08ae9d
--- /dev/null
+++ b/internal/cli/oauth_pages_test.go
@@ -0,0 +1,70 @@
+package cli
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestOAuthCallbackSuccessPageUsesFinalGlobalDesign(t *testing.T) {
+ html := renderOAuthCallbackSuccessPage(callbackPageConfig{Region: regionGlobal})
+
+ for _, want := range []string{
+ "agora-page",
+ "Agora%20Logo%20Crisp.webp",
+ "You are now authenticated with Agora CLI",
+ "This browser step completed successfully. The CLI can now use this account as your active local configuration.",
+ "agora auth status",
+ "You can close this window and return to your terminal.",
+ } {
+ if !strings.Contains(html, want) {
+ t.Fatalf("expected global login page to contain %q", want)
+ }
+ }
+
+ for _, forbidden := range []string{"agora whoami", "global-minimal", "global-deck"} {
+ if strings.Contains(html, forbidden) {
+ t.Fatalf("global login page should not contain legacy variant content %q", forbidden)
+ }
+ }
+}
+
+func TestOAuthCallbackSuccessPageUsesFinalChinaDesign(t *testing.T) {
+ html := renderOAuthCallbackSuccessPage(callbackPageConfig{Region: regionCN})
+
+ for _, want := range []string{
+ "shengwang-page",
+ "brand-logo-cn",
+ "你已成功登录声网 CLI",
+ "此浏览器步骤已完成。CLI 现在可以将此账号作为当前本地配置继续使用。",
+ "agora auth status",
+ "你可以关闭此页面,回到终端继续操作。",
+ } {
+ if !strings.Contains(html, want) {
+ t.Fatalf("expected China login page to contain %q", want)
+ }
+ }
+
+ for _, forbidden := range []string{"agora whoami", "cn-console", "cn-silk"} {
+ if strings.Contains(html, forbidden) {
+ t.Fatalf("China login page should not contain legacy variant content %q", forbidden)
+ }
+ }
+}
+
+func TestOAuthCallbackErrorPageKeepsRegionBranding(t *testing.T) {
+ html := renderOAuthCallbackErrorPage(callbackPageConfig{Region: regionCN}, "Login state mismatch", "Return to the terminal and restart login.")
+
+ for _, want := range []string{
+ "shengwang-page is-error",
+ "Login state mismatch",
+ "Return to the terminal and restart login.",
+ } {
+ if !strings.Contains(html, want) {
+ t.Fatalf("expected error login page to contain %q", want)
+ }
+ }
+
+ if strings.Contains(html, "agora auth status") {
+ t.Fatal("error login page should not show success action command")
+ }
+}
From 4c69e17f632471f8224e71090e9ec77c8aa8d36f Mon Sep 17 00:00:00 2001
From: sunshinexcode <24xinhui@163.com>
Date: Tue, 7 Jul 2026 15:08:39 +0800
Subject: [PATCH 2/3] fix(oauth): isolate callback page branding by region
---
internal/cli/oauth_pages.go | 58 ++++++++++++++++++--------------
internal/cli/oauth_pages_test.go | 22 +++++++++++-
2 files changed, 54 insertions(+), 26 deletions(-)
diff --git a/internal/cli/oauth_pages.go b/internal/cli/oauth_pages.go
index ca405af..7ac4e18 100644
--- a/internal/cli/oauth_pages.go
+++ b/internal/cli/oauth_pages.go
@@ -66,7 +66,7 @@ func renderOAuthCallbackPage(content loginPageContent, isError bool, errorTitle,
var b strings.Builder
b.WriteString(`Agora CLI Login
Date: Tue, 7 Jul 2026 15:29:05 +0800
Subject: [PATCH 3/3] fix(auth): tune OAuth login page branding
---
internal/cli/oauth_pages.go | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/internal/cli/oauth_pages.go b/internal/cli/oauth_pages.go
index 7ac4e18..e60ea3d 100644
--- a/internal/cli/oauth_pages.go
+++ b/internal/cli/oauth_pages.go
@@ -101,7 +101,7 @@ func loginPageCSS(pageClass string) string {
body{margin:0;min-height:100vh;font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;-webkit-font-smoothing:antialiased}
.page{min-height:100vh;display:grid;place-items:center;padding:64px 24px;color:var(--ink);background:var(--bg)}
.card{width:min(760px,100%);padding:40px;border:1px solid var(--line);border-radius:20px;background:var(--panel);box-shadow:var(--shadow)}
-.brand{display:inline-flex;align-items:center;gap:10px;color:var(--ink);font-size:16px;font-weight:700;letter-spacing:-.01em}
+.brand{display:inline-flex;align-items:center;gap:10px;color:var(--ink);font-size:22px;font-weight:800;letter-spacing:-.01em}
.brand-logo{display:inline-flex;align-items:center;flex:0 0 auto}
.brand-logo svg{display:block;width:100%;height:100%}
.brand-logo img{display:block;width:100%;height:auto}
@@ -124,9 +124,9 @@ code{padding:5px 9px;border:1px solid var(--code-line);border-radius:8px;backgro
if pageClass == "shengwang-page" {
b.WriteString(`
-.shengwang-page{--ink:#152033;--muted:#617083;--primary:#1469ff;--brand:#1469ff;--line:#dce7f5;--panel:#fff;--shadow:0 20px 55px rgba(21,45,85,.1);--next-line:#dce7f5;--next-bg:#f7fbff;--next-text:#46576c;--code-line:#cfe0f8;--code-bg:#eef6ff;--bg:linear-gradient(180deg,#f6faff 0%,#fff 58%,#f9fbfd 100%)}
-.brand-logo-cn{width:88px;height:auto;color:var(--brand)}
-.brand-logo-cn svg{width:88px;height:auto}
+.shengwang-page{--ink:#152033;--muted:#617083;--primary:#0b9dfd;--brand:#0b9dfd;--line:#dce7f5;--panel:#fff;--shadow:0 20px 55px rgba(21,45,85,.1);--next-line:#dce7f5;--next-bg:#f7fbff;--next-text:#46576c;--code-line:#cfe0f8;--code-bg:#eef6ff;--bg:linear-gradient(180deg,#f6faff 0%,#fff 58%,#f9fbfd 100%)}
+.brand-logo-cn{width:58px;height:auto;color:var(--brand)}
+.brand-logo-cn svg{width:58px;height:auto}
`)
return b.String()
}