Skip to content

Feature/add sms notification - #4306

Open
jiong-chen wants to merge 8 commits into
QuantumNous:mainfrom
jiong-chen:feature/add-sms-notification
Open

Feature/add sms notification#4306
jiong-chen wants to merge 8 commits into
QuantumNous:mainfrom
jiong-chen:feature/add-sms-notification

Conversation

@jiong-chen

@jiong-chen jiong-chen commented Apr 17, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

为系统增加了多渠道短信通知能力。后端实现了统一的短信发送抽象层,管理员可通过面板配置密钥,用户在个人中心绑定手机号即可接收告警/通知。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

工作原理:

配置层:在系统设置中增加了 SMS 配置项,用于存储服务商凭证。

发送层:实现了独立的 SMS 服务模块,根据配置动态加载对应的 SDK 或 HTTP 请求逻辑。

调用层:在原有的通知触发逻辑中增加了 type=sms 的判断分支,仅当用户已绑定手机且余额/配置正常时执行。

签名算法:针对阿里云(HMAC-SHA1)、腾讯云(TC3-HMAC-SHA256)和 SendCloud(MD5)的不同鉴权方式进行了适配。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)
image

image

Summary by CodeRabbit

  • New Features
    • SMS notifications added as a delivery option for users.
    • Users can add and validate an SMS phone number in notification settings (international format enforced).
    • Administrators can configure SMS providers and credentials (Aliyun, SendCloud, Tencent, or custom HTTP) via system settings.
    • Quota warning alerts can be sent via SMS with localized validation/error messages.

cjy0123 added 2 commits April 17, 2026 16:41
  架构设计:
  - 管理员在「系统设置」中配置短信服务商和凭证
  - 用户在「个人设置 → 通知配置」中选择"短信通知",只需填写手机号

  支持 4 种短信服务商:
  1. 阿里云短信 — POP v1 签名 (HMAC-SHA1),纯 HTTP 调用 dysmsapi.aliyuncs.com
  2. SendCloud — MD5 签名,POST form 到 sendcloud.net/smsapi/send
  3. 腾讯云短信 — TC3-HMAC-SHA256 签名,POST JSON 到 sms.tencentcloudapi.com
  4. 通用 HTTP 接口 — 自定义 URL/方法/模板,支持 {{phone}}/{{title}}/{{content}} 占位符
@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds SMS notification support: new SMS configuration constants, option persistence, phone-number capture and validation, provider-specific sending (Aliyun, SendCloud, Tencent, custom HTTP), integration into notification/quota flows, and corresponding frontend system/personal settings and i18n entries.

Changes

Cohort / File(s) Summary
Constants
common/constants.go
Added SMS provider selector and exported credential/template/custom HTTP variables (defaults: SMSCustomMethod="POST").
Options persistence
model/option.go
Seeded SMS option keys in InitOptionMap and updateOptionMap; validates/uppercases SMSCustomMethod with allowlist and POST fallback.
Controller / Validation
controller/user.go, controller/option.go
Accept sms_phone_number in UpdateUserSetting; validate pattern ^+?[0-9]{7,15}$ and require configured provider; extended sensitive-key filtering to include SMS credential keys.
DTOs
dto/user_settings.go
Added SmsPhoneNumber to UserSetting and new NotifyTypeSms constant.
Service: SMS implementation
service/sms_notify.go
New file implementing sendSmsNotify and provider-specific senders: Aliyun (signed GET), SendCloud (MD5 form POST), Tencent (TC3-HMAC-SHA256 JSON POST), and Custom HTTP with template interpolation and SSRF/worker handling.
Service: Integration
service/user_notify.go, service/quota.go
Dispatches SMS in NotifyUser and formats SMS-specific quota messages for quota notifications.
i18n
i18n/keys.go, i18n/locales/en.yaml, i18n/locales/zh-CN.yaml, i18n/locales/zh-TW.yaml
Added three i18n keys/translations: sms_phone_empty, sms_phone_invalid, sms_not_configured.
Frontend — Personal settings
web/classic/src/components/settings/PersonalSetting.jsx, web/classic/src/components/settings/personal/cards/NotificationSettings.jsx
Hydrate/save sms_phone_number; add SMS notification radio and conditional phone input with validation and guidance.
Frontend — System settings
web/classic/src/components/settings/SystemSetting.jsx
New SMS configuration card: provider selection, provider-specific credential/template fields, custom HTTP method/template, change-detection submit flow, and secret-field handling.
Misc Minor i18n key additions and wiring across backend/frontend to support the new notify type and settings.

Sequence Diagram(s)

sequenceDiagram
    participant User as Client
    participant API as API Server
    participant Notify as Notification Service
    participant Provider as SMS Provider (Aliyun/SendCloud/Tencent/Custom)

    User->>API: PUT /api/user/setting (sms_phone_number)
    activate API
    API->>API: Validate phone format & SMSProvider configured
    API->>API: Persist settings
    deactivate API

    Note over User: Trigger event (e.g., quota warning)

    User->>API: Action triggers notification
    activate API
    API->>Notify: NotifyUser(NotifyTypeSms, payload)
    activate Notify
    Notify->>Notify: Retrieve user's sms_phone_number
    Notify->>Notify: Build template values and content
    Notify->>Provider: Send via selected provider
    alt Aliyun
        Provider->>Provider: GET with HMAC-SHA1 signature
    else SendCloud
        Provider->>Provider: POST form with MD5 sign
    else Tencent
        Provider->>Provider: POST JSON with TC3-HMAC-SHA256
    else Custom
        Provider->>Provider: Interpolated GET/POST (SSRF/worker checks)
    end
    Provider-->>Notify: Response (success/error)
    Notify-->>API: Result
    deactivate Notify
    deactivate API
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble configs, keys in tow,

Aliyun, SendCloud, Tencent go!
Numbers checked with regex grace,
Templates stitched and signed in place,
Hoppy alerts hop to each phone's space. 📱✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/add sms notification' directly and clearly describes the main change: adding SMS notification functionality across the backend and frontend systems.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (2)
service/sms_notify.go (2)

398-432: Minor: hardcoded Content-Type and User-Agent reduce custom-provider flexibility.

Content-Type: application/json; charset=utf-8 is forced regardless of what the admin's template body looks like, which will break integrations that expect application/x-www-form-urlencoded or other formats. Consider either auto-detecting from the template, or exposing the headers as an admin-configurable field. Also, the OneAPI-SMS-Notify/1.0 User-Agent reads like a leftover from an upstream fork — NewAPI-SMS-Notify/1.0 would match this project's branding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/sms_notify.go` around lines 398 - 432, The code currently forces
"Content-Type: application/json; charset=utf-8" and "User-Agent:
OneAPI-SMS-Notify/1.0" for both workerReq.Headers and req.Header, breaking
non-JSON providers and using the wrong branding; update the logic in the
send/custom provider path (references: workerReq.Headers, DoWorkerRequest,
finalBody, finalURL, req.Header.Set, method) to use headers supplied by the
admin/provider configuration (if present) or fall back to auto-detecting
Content-Type from the template/body (e.g., form-urlencoded vs JSON) before
setting headers, and change the default User-Agent to "NewAPI-SMS-Notify/1.0";
ensure both the worker-request branch (workerReq.Headers) and direct http branch
(req.Header) use this configurable/default behavior.

260-265: Consider making provider region/endpoint configurable.

Tencent region is hardcoded to ap-guangzhou and Aliyun uses only the global dysmsapi.aliyuncs.com endpoint. Users with templates provisioned in other regions (e.g., Tencent ap-singapore, Aliyun international dysmsapi.ap-southeast-1.aliyuncs.com) will either get region-mismatch errors or be unable to use the integration. A new optional admin field (e.g., SMSTencentRegion, SMSAliyunEndpoint) would unblock those deployments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/sms_notify.go` around lines 260 - 265, The region/endpoint for SMS
providers is hardcoded causing region-mismatch errors; update sendTencentSms to
accept a region parameter (or read from a config field like SMSTencentRegion)
and use that instead of the fixed "ap-guangzhou", and likewise make the Aliyun
SMS calls use a configurable endpoint (e.g., SMSAliyunEndpoint) with the current
values as defaults; add the new optional admin/config fields, pass them into
sendTencentSms (and the Aliyun send function), and ensure fallback to the
original hardcoded values when the config fields are empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@common/constants.go`:
- Around line 88-89: The comment for the SMSProvider constant is missing the
"sendcloud" provider; update the comment near the SMSProvider variable (symbol
SMSProvider in common/constants.go) to enumerate all supported providers
including "sendcloud" (matching the switch in service/sms_notify.go and options
in SystemSetting.jsx) so the comment lists aliyun, tencent, sendcloud, custom.

In `@controller/option.go`:
- Around line 74-79: The current blocklist in controller/option.go hides
SMSCustomUrl and SMSCustomTemplate (and explicitly lists some IDs) which breaks
admin UX by masking non-sensitive configuration; update the filtering so
SMSCustomUrl and SMSCustomTemplate are removed from the sensitive-keys check and
only true secrets/keys remain (rely on existing suffix-based filters like
*Secret/*Key), and if you intentionally want to treat IDs as sensitive, add a
brief comment next to the SMSAliyunAccessKeyId / SMSTencentSecretId entries
explaining that policy decision.

In `@controller/user.go`:
- Around line 1191-1210: Replace the three raw c.JSON responses for SMS
validation (empty phone, invalid format using phoneRegex, and missing
common.SMSProvider) with the i18n-aware helper common.ApiErrorI18n so messages
go through localization; locate the checks that reference req.SmsPhoneNumber,
phoneRegex (regexp.MustCompile(`^\+?[0-9]{7,15}$`)) and the conditional on
common.SMSProvider and call common.ApiErrorI18n(c,
"<appropriate_error_key_or_message>") or the existing error key constants used
elsewhere in this handler instead of c.JSON so the responses are localized.

In `@model/option.go`:
- Around line 380-381: The assignment in the switch case for "SMSCustomMethod"
unconditionally sets common.SMSCustomMethod to value, which can overwrite the
default "POST" with an empty or invalid string; change the handler in the case
"SMSCustomMethod" to validate/sanitize value before assignment: if value is
empty or not a valid HTTP method (e.g., not one of GET, POST, PUT, DELETE,
PATCH, OPTIONS, HEAD), do not overwrite common.SMSCustomMethod and leave the
default intact, otherwise assign the sanitized/uppercased method to
common.SMSCustomMethod.
- Around line 352-383: The GET /api/option filtering in controller/option.go
currently omits SMSTencentSmsSdkAppId from the sensitive-keys filter; update the
filter used in the handler (the function that builds the options response for
GET /api/option) to include "SMSTencentSmsSdkAppId" so it is excluded from
responses, and also review/add any of the other SMS fields from model/option.go
(SMSAliyunSignName, SMSAliyunTemplateCode, SMSSendCloudTemplateId,
SMSTencentSignName, SMSTencentTemplateId, SMSCustomMethod) to the same filter if
your threat model requires them to be hidden. Ensure you modify the same
string-list or matching logic used for keys ending with Secret/Key/Token so the
change follows the existing filtering approach.

In `@service/quota.go`:
- Around line 444-448: The SMS template values use values[0]=remainQuota and
values[1]=threshold but when BillingSource == BillingSourceSubscription the
computed remainQuota (relayInfo.UserQuota - consumeQuota) isn't representative
and checkAndSendSubscriptionQuotaNotify currently lacks an SMS path; update
checkAndSendSubscriptionQuotaNotify to mirror the SMS branch logic used where
sendAliyunSms/sendSendCloudSms expect templateValues[0]→user_money and
templateValues[1]→balance_warn, compute remainQuota consistently (use the same
relayInfo.UserQuota - consumeQuota or appropriate subscription remaining
calculation), set values = []interface{}{FormatQuota(remainQuota),
FormatQuota(threshold)}, and call the same SMS send helpers so subscription
quota notifications match the non-subscription SMS behavior.

In `@service/sms_notify.go`:
- Around line 3-23: The file imports and directly uses encoding/json; replace
all direct json.Marshal and json.Unmarshal calls in service/sms_notify.go with
the repository wrappers common.Marshal(...) and common.Unmarshal(..., &v)
(occurrences noted around the JSON call sites referenced in the review: lines
~109, 161, 190, 235, 287, 349, 366), and remove the direct "encoding/json"
import from the import block; ensure error handling and return types remain the
same after swapping to common.Marshal/common.Unmarshal so functions that call
these (e.g., any methods in this file that reference those lines) continue to
behave identically.

In `@web/src/i18n/locales/en.json`:
- Line 3301: There is a duplicate i18n key "敏感信息不会发送到前端显示" in en.json; locate
both occurrences (one earlier and the second at the shown diff) and remove the
redundant entry or consolidate them so only a single definition remains; ensure
the remaining value is the correct English translation ("Sensitive information
will not be sent to the frontend" or the intended text) and run a quick JSON
lint to keep the file valid after removal.

In `@web/src/i18n/locales/fr.json`:
- Line 3267: Remove the duplicate i18n key "敏感信息不会发送到前端显示" by deleting the later
occurrence and keeping the original entry (ensure the single remaining
translation is correct); search the locale JSON for any other duplicates of that
key and remove them, then validate the JSON (lint/parse) to confirm no
duplicate-key overwrite issues remain.

In `@web/src/i18n/locales/ja.json`:
- Line 3248: Remove the duplicate JSON entry for the Chinese key "敏感信息不会发送到前端显示"
(the later translation "機密情報はフロントエンドに表示されません") so the original translation
("機密情報はフロントエンドに送信されず、表示されることはありません") remains authoritative; if you actually need
a different SMS-specific wording, introduce a new Chinese source key and update
the calling component instead, then run the i18n pipeline (bun run i18n:extract,
bun run i18n:sync, bun run i18n:lint) to keep locale files consistent.
- Around line 3216-3249: The ja.json SMS block introduced a duplicate key
"敏感信息不会发送到前端显示" (already present elsewhere in ja.json) which will overwrite the
original—remove the duplicate entry from the SMS section (the new one around the
SMS keys) and keep the original translation; then ensure the 34 SMS keys you
added (e.g., "短信通知", "短信服务商", "阿里云短信", "腾讯云短信", "通用HTTP接口", "手机号码", "短信签名",
"模板Code", "模板ID", "接口地址", "请求方法", "请求模板", etc.) are propagated to other locales
by running the localization sync and lint commands: run "bun run i18n:sync" to
add these keys to zh-TW.json and other locale files, then run "bun run
i18n:lint" to validate and fix any structural issues.

In `@web/src/i18n/locales/ru.json`:
- Line 3281: There is a duplicated JSON translation key "敏感信息不会发送到前端显示" — remove
the later occurrence (or consolidate its value with the original) so the key
appears only once in the ru locale, then re-run the i18n pipeline (bun run
i18n:extract, bun run i18n:sync, bun run i18n:lint) to ensure no other
duplicates or lint errors remain.

In `@web/src/i18n/locales/vi.json`:
- Around line 3748-3749: The JSON contains duplicate translation keys "未配置" and
"敏感信息不会发送到前端显示" that override earlier entries; locate the earlier definitions of
those keys in the same vi.json and remove or reconcile these later duplicate
entries so each Chinese source string appears only once (keeping the intended
Vietnamese value), or consolidate by updating the original key's value instead
of adding duplicates; ensure the file remains a flat JSON object and validate no
other duplicate Chinese keys exist.

In `@web/src/i18n/locales/zh-CN.json`:
- Line 2535: Remove the duplicate JSON key "敏感信息不会发送到前端显示" from this locale file
by deleting this occurrence so only the original mapping remains (the earlier
entry already has the identical value); ensure the surrounding commas remain
valid and the JSON stays well-formed after removal.

---

Nitpick comments:
In `@service/sms_notify.go`:
- Around line 398-432: The code currently forces "Content-Type:
application/json; charset=utf-8" and "User-Agent: OneAPI-SMS-Notify/1.0" for
both workerReq.Headers and req.Header, breaking non-JSON providers and using the
wrong branding; update the logic in the send/custom provider path (references:
workerReq.Headers, DoWorkerRequest, finalBody, finalURL, req.Header.Set, method)
to use headers supplied by the admin/provider configuration (if present) or fall
back to auto-detecting Content-Type from the template/body (e.g.,
form-urlencoded vs JSON) before setting headers, and change the default
User-Agent to "NewAPI-SMS-Notify/1.0"; ensure both the worker-request branch
(workerReq.Headers) and direct http branch (req.Header) use this
configurable/default behavior.
- Around line 260-265: The region/endpoint for SMS providers is hardcoded
causing region-mismatch errors; update sendTencentSms to accept a region
parameter (or read from a config field like SMSTencentRegion) and use that
instead of the fixed "ap-guangzhou", and likewise make the Aliyun SMS calls use
a configurable endpoint (e.g., SMSAliyunEndpoint) with the current values as
defaults; add the new optional admin/config fields, pass them into
sendTencentSms (and the Aliyun send function), and ensure fallback to the
original hardcoded values when the config fields are empty.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ad5713e6-6b1a-47e0-9865-34630f9809b8

📥 Commits

Reviewing files that changed from the base of the PR and between 2834740 and d7d35e9.

📒 Files selected for processing (17)
  • common/constants.go
  • controller/option.go
  • controller/user.go
  • dto/user_settings.go
  • model/option.go
  • service/quota.go
  • service/sms_notify.go
  • service/user_notify.go
  • web/src/components/settings/PersonalSetting.jsx
  • web/src/components/settings/SystemSetting.jsx
  • web/src/components/settings/personal/cards/NotificationSettings.jsx
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json

Comment thread common/constants.go Outdated
Comment thread controller/option.go Outdated
Comment thread controller/user.go Outdated
Comment thread model/option.go
Comment on lines +352 to +383
case "SMSProvider":
common.SMSProvider = value
case "SMSAliyunAccessKeyId":
common.SMSAliyunAccessKeyId = value
case "SMSAliyunAccessKeySecret":
common.SMSAliyunAccessKeySecret = value
case "SMSAliyunSignName":
common.SMSAliyunSignName = value
case "SMSAliyunTemplateCode":
common.SMSAliyunTemplateCode = value
case "SMSSendCloudSmsUser":
common.SMSSendCloudSmsUser = value
case "SMSSendCloudSmsKey":
common.SMSSendCloudSmsKey = value
case "SMSSendCloudTemplateId":
common.SMSSendCloudTemplateId = value
case "SMSTencentSecretId":
common.SMSTencentSecretId = value
case "SMSTencentSecretKey":
common.SMSTencentSecretKey = value
case "SMSTencentSmsSdkAppId":
common.SMSTencentSmsSdkAppId = value
case "SMSTencentSignName":
common.SMSTencentSignName = value
case "SMSTencentTemplateId":
common.SMSTencentTemplateId = value
case "SMSCustomUrl":
common.SMSCustomUrl = value
case "SMSCustomMethod":
common.SMSCustomMethod = value
case "SMSCustomTemplate":
common.SMSCustomTemplate = value

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Enumerate which SMS keys are actually filtered by controller/option.go and which are returned.
rg -nP -C3 '(SMSAliyunAccessKeyId|SMSSendCloudSmsUser|SMSTencentSecretId|SMSCustomUrl|SMSCustomTemplate|HasSuffix\(k,\s*"(Secret|Key|Token|secret|api_key)"\))' controller/option.go
echo "---"
# Find any other place SMS* options are read/written (to confirm no additional exposure paths)
rg -nP -C2 '\bSMS[A-Z][A-Za-z]+' --type=go

Repository: QuantumNous/new-api

Length of output: 7675


Fix credential exposure: SMSTencentSmsSdkAppId is returned unfiltered in GET /api/option/.

The filtering logic in controller/option.go (lines 69–79) uses suffix matching (*Secret, *Key, Token) and explicitly lists 5 SMS keys to exclude. However, SMSTencentSmsSdkAppId matches none of these rules and is returned to the frontend. While nominally a tenant ID rather than a secret, it identifies the Tencent application and should be filtered. Also verify that other unfiltered SMS keys (SMSAliyunSignName, SMSAliyunTemplateCode, SMSSendCloudTemplateId, SMSTencentSignName, SMSTencentTemplateId, SMSCustomMethod) do not require filtering based on your threat model.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model/option.go` around lines 352 - 383, The GET /api/option filtering in
controller/option.go currently omits SMSTencentSmsSdkAppId from the
sensitive-keys filter; update the filter used in the handler (the function that
builds the options response for GET /api/option) to include
"SMSTencentSmsSdkAppId" so it is excluded from responses, and also review/add
any of the other SMS fields from model/option.go (SMSAliyunSignName,
SMSAliyunTemplateCode, SMSSendCloudTemplateId, SMSTencentSignName,
SMSTencentTemplateId, SMSCustomMethod) to the same filter if your threat model
requires them to be hidden. Ensure you modify the same string-list or matching
logic used for keys ending with Secret/Key/Token so the change follows the
existing filtering approach.

Comment thread model/option.go Outdated
Comment thread web/src/i18n/locales/fr.json Outdated
Comment thread web/src/i18n/locales/ja.json Outdated
Comment thread web/src/i18n/locales/ru.json Outdated
Comment thread web/src/i18n/locales/vi.json Outdated
Comment thread web/src/i18n/locales/zh-CN.json Outdated
- 补全SMSProvider注释中遗漏的sendcloud服务商
- 调整敏感字段过滤:移除非敏感的SMSCustomUrl/SMSCustomTemplate,新增SMSTencentSmsSdkAppId
- SMS验证错误响应改用i18n国际化(ApiErrorI18n),新增3组翻译(en/zh-CN/zh-TW)
- SMSCustomMethod赋值前增加HTTP方法校验,空值保留默认POST
- sms_notify.go中encoding/json替换为common.Marshal/Unmarshal(遵循Rule 1)
- User-Agent从OneAPI改为NewAPI匹配项目品牌
- 移除6个locale JSON文件中重复的i18n key
- 订阅额度通知(checkAndSendSubscriptionQuotaNotify)补充SMS分支

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
model/option.go (1)

380-389: ⚠️ Potential issue | 🟡 Minor

Empty SMSCustomMethod can keep a stale non-default method in memory.

At Line 385-387, empty input does not reset common.SMSCustomMethod. If it was previously GET, saving an empty value can continue using GET until restart instead of defaulting to POST.

🔧 Suggested fix
 	case "SMSCustomMethod":
-		upperMethod := strings.ToUpper(value)
+		upperMethod := strings.ToUpper(strings.TrimSpace(value))
 		switch upperMethod {
 		case "GET", "POST", "PUT", "DELETE", "PATCH":
 			common.SMSCustomMethod = upperMethod
 		case "":
-			// 空值保留默认的 POST
+			common.SMSCustomMethod = "POST"
 		default:
 			common.SMSCustomMethod = "POST"
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model/option.go` around lines 380 - 389, The empty-input branch in the
"SMSCustomMethod" case currently leaves common.SMSCustomMethod unchanged,
allowing a stale method to persist; change the "" branch in the switch over
upperMethod inside the "SMSCustomMethod" case to explicitly set
common.SMSCustomMethod = "POST" (the default) so that empty input resets the
value; ensure the allowed-method switch (case
"GET","POST","PUT","DELETE","PATCH") still assigns upperMethod and the default
branch also falls back to "POST".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@service/quota.go`:
- Around line 444-448: The SMS branch currently clears the content variable
(content = "") which strips the rendered message before calling the custom SMS
provider; remove that assignment and ensure content remains the human-readable
message used elsewhere, while still populating values (e.g., keep values =
[]interface{}{logger.FormatQuota(remainQuota), logger.FormatQuota(threshold)}).
Update the branch that checks notifyType == dto.NotifyTypeSms (and the other
similar SMS branches around lines ~500) to stop clearing content so {{content}}
is available to service/sms_notify.go and custom providers can receive the
remaining-quota/threshold message.

In `@service/sms_notify.go`:
- Around line 336-358: The code currently treats a nil Response.Error as success
even when result.Response.SendStatusSet is missing or empty; update the
post-unmarshal checks in sms_notify.go (around the result struct handling) to
explicitly reject an empty or missing SendStatusSet: after checking
result.Response.Error, verify that len(result.Response.SendStatusSet) > 0 and
return an error (e.g. "tencent sms failed: empty SendStatusSet") if it is zero
or nil before inspecting SendStatusSet[0].Code; this ensures you don't report
success when per-recipient results are absent.
- Around line 392-442: The worker branch is rejecting valid http:// endpoints
because it calls DoWorkerRequest without the same URL validation/allowances as
the direct branch; before constructing/dispatching WorkerRequest in the
system_setting.EnableWorker() path, run the same
common.ValidateURLWithFetchSetting(...) check used in the else branch to allow
http per fetch settings, and if WorkerRequest/DoWorkerRequest supports an
explicit flag (e.g., AllowInsecureHTTP or similar tied to
WorkerAllowHttpImageRequestEnabled) set that flag on WorkerRequest so the worker
will not force HTTPS; update usages around WorkerRequest and DoWorkerRequest to
preserve POST body handling (workerReq.Body) and to mirror the non-worker
header/validation behavior.

In `@web/src/i18n/locales/ja.json`:
- Around line 3216-3248: The ja.json locale is missing backend SMS validation
keys used by the server; add Japanese translations for the keys
setting.sms_phone_empty, setting.sms_phone_invalid, and
setting.sms_not_configured so validation errors from controller/user.go (SMS
validation) and i18n key lookups return human-readable messages; mirror the
existing English/Chinese semantics from the UI strings (e.g., use
"電話番号を入力してください" for sms_phone_empty, "電話番号の形式が正しくありません" for sms_phone_invalid,
and "SMSサービスが設定されていません" for sms_not_configured).

---

Duplicate comments:
In `@model/option.go`:
- Around line 380-389: The empty-input branch in the "SMSCustomMethod" case
currently leaves common.SMSCustomMethod unchanged, allowing a stale method to
persist; change the "" branch in the switch over upperMethod inside the
"SMSCustomMethod" case to explicitly set common.SMSCustomMethod = "POST" (the
default) so that empty input resets the value; ensure the allowed-method switch
(case "GET","POST","PUT","DELETE","PATCH") still assigns upperMethod and the
default branch also falls back to "POST".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f75dee1-f3d9-4f9f-8480-337d50a33898

📥 Commits

Reviewing files that changed from the base of the PR and between d7d35e9 and 2e7a732.

📒 Files selected for processing (16)
  • common/constants.go
  • controller/option.go
  • controller/user.go
  • i18n/keys.go
  • i18n/locales/en.yaml
  • i18n/locales/zh-CN.yaml
  • i18n/locales/zh-TW.yaml
  • model/option.go
  • service/quota.go
  • service/sms_notify.go
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json
✅ Files skipped from review due to trivial changes (7)
  • i18n/locales/zh-CN.yaml
  • i18n/locales/zh-TW.yaml
  • i18n/locales/en.yaml
  • i18n/keys.go
  • common/constants.go
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/ru.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/i18n/locales/fr.json

Comment thread service/quota.go
Comment thread service/sms_notify.go
Comment thread service/sms_notify.go Outdated
Comment on lines +3216 to +3248
"短信通知": "SMS通知",
"短信服务商": "SMSプロバイダー",
"阿里云短信": "Alibaba Cloud SMS",
"腾讯云短信": "Tencent Cloud SMS",
"通用HTTP接口": "カスタムHTTP API",
"手机号码": "電話番号",
"请输入接收短信的手机号码": "SMSを受信する電話番号を入力してください",
"请输入手机号码": "電話番号を入力してください",
"手机号码格式不正确": "電話番号の形式が正しくありません",
"短信服务由管理员统一配置,您只需填写接收通知的手机号码": "SMSサービスは管理者が設定します。電話番号のみ入力してください",
"短信签名": "SMS署名",
"模板Code": "テンプレートコード",
"请输入模板Code": "テンプレートコードを入力してください",
"模板ID": "テンプレートID",
"请输入模板ID": "テンプレートIDを入力してください",
"接口地址": "API URL",
"请输入短信接口地址": "SMS API URLを入力してください",
"请求方法": "リクエストメソッド",
"请求模板": "リクエストテンプレート",
"请输入请求模板": "リクエストテンプレートを入力してください",
"短信接口地址必须以http://或https://开头": "SMS API URLはhttp://またはhttps://で始まる必要があります",
"阿里云短信配置说明": "Alibaba Cloud SMS設定",
"腾讯云短信配置说明": "Tencent Cloud SMS設定",
"通用HTTP接口说明": "カスタムHTTP API説明",
"请输入短信签名": "SMS署名を入力してください",
"在阿里云短信服务控制台创建签名和模板后获取相关参数": "Alibaba Cloud SMSコンソールで署名とテンプレートを作成した後、パラメータを取得してください",
"在腾讯云短信控制台创建应用、签名和模板后获取相关参数": "Tencent Cloud SMSコンソールでアプリ、署名、テンプレートを作成した後、パラメータを取得してください",
"支持模板变量: {{phone}} (手机号), {{title}} (通知标题), {{content}} (通知内容)": "テンプレート変数: {{phone}} (電話番号), {{title}} (通知タイトル), {{content}} (通知内容)",
"短信模板中需包含一个变量用于接收通知内容": "SMSテンプレートには通知内容用の変数が1つ必要です",
"配置短信服务": "SMSサービスの設定",
"用以支持短信通知推送,用户可在个人设置中选择短信通知方式": "SMS通知をサポートします。ユーザーは個人設定でSMS通知を選択できます",
"未配置": "未設定",
"保存短信设置": "SMS設定を保存",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add missing backend SMS error i18n keys for Japanese.

This block adds UI SMS strings, but the backend also returns setting.sms_phone_empty, setting.sms_phone_invalid, and setting.sms_not_configured (see controller/user.go Line 1188 and i18n/keys.go Line 249). I don’t see these keys in ja.json, so Japanese users will see raw key names on validation errors.

🛠️ Proposed fix
     "保存短信设置": "SMS設定を保存",
+    "setting.sms_phone_empty": "電話番号を入力してください",
+    "setting.sms_phone_invalid": "電話番号の形式が正しくありません",
+    "setting.sms_not_configured": "SMSサービスが未設定です。管理者にお問い合わせください",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/ja.json` around lines 3216 - 3248, The ja.json locale is
missing backend SMS validation keys used by the server; add Japanese
translations for the keys setting.sms_phone_empty, setting.sms_phone_invalid,
and setting.sms_not_configured so validation errors from controller/user.go (SMS
validation) and i18n key lookups return human-readable messages; mirror the
existing English/Chinese semantics from the UI strings (e.g., use
"電話番号を入力してください" for sms_phone_empty, "電話番号の形式が正しくありません" for sms_phone_invalid,
and "SMSサービスが設定されていません" for sms_not_configured).

…ication

# Conflicts:
#	controller/option.go
#	web/src/i18n/locales/zh-CN.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
service/quota.go (1)

481-485: ⚠️ Potential issue | 🟠 Major

Preserve SMS content for custom-provider templates.

On Line 483 and Line 539, setting content = "" drops the human-readable message for custom HTTP SMS integrations that render {{content}}, so users receive incomplete notifications.

🛠️ Proposed fix
-		} else if notifyType == dto.NotifyTypeSms {
-			// SMS短信使用模板变量:values[0]=当前余额, values[1]=告警阈值
-			content = ""
-			remainQuota := relayInfo.UserQuota - consumeQuota
-			values = []interface{}{logger.FormatQuota(remainQuota), logger.FormatQuota(threshold)}
+		} else if notifyType == dto.NotifyTypeSms {
+			// 保留 content 以兼容 custom HTTP 模板中的 {{content}}
+			content = "当前剩余额度为 {{value}},已触发告警阈值 {{value}},请及时充值。"
+			remainQuota := relayInfo.UserQuota - consumeQuota
+			values = []interface{}{logger.FormatQuota(remainQuota), logger.FormatQuota(threshold)}
 		}
@@
-		} else if notifyType == dto.NotifyTypeSms {
-			// SMS短信使用模板变量:values[0]=当前余额, values[1]=告警阈值
-			content = ""
-			values = []interface{}{logger.FormatQuota(int(remaining)), logger.FormatQuota(threshold)}
+		} else if notifyType == dto.NotifyTypeSms {
+			content = "当前剩余额度为 {{value}},已触发告警阈值 {{value}},请及时充值。"
+			values = []interface{}{logger.FormatQuota(int(remaining)), logger.FormatQuota(threshold)}
 		}

Also applies to: 537-540

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/quota.go` around lines 481 - 485, In the NotifyTypeSms branches
(where notifyType == dto.NotifyTypeSms) don't overwrite the existing content
with an empty string; instead remove the assignments content = "" and simply set
values = []interface{}{logger.FormatQuota(remainQuota),
logger.FormatQuota(threshold)} so the original human-readable content remains
for custom HTTP SMS templates that use {{content}}; make the same change in the
other symmetric SMS branch (the second block that also sets content = "") so
both SMS paths preserve the message body while still populating values using
relayInfo.UserQuota, consumeQuota, threshold and logger.FormatQuota.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@service/quota.go`:
- Around line 481-485: In the NotifyTypeSms branches (where notifyType ==
dto.NotifyTypeSms) don't overwrite the existing content with an empty string;
instead remove the assignments content = "" and simply set values =
[]interface{}{logger.FormatQuota(remainQuota), logger.FormatQuota(threshold)} so
the original human-readable content remains for custom HTTP SMS templates that
use {{content}}; make the same change in the other symmetric SMS branch (the
second block that also sets content = "") so both SMS paths preserve the message
body while still populating values using relayInfo.UserQuota, consumeQuota,
threshold and logger.FormatQuota.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bdd86e8f-08bf-4005-8011-c51f91394014

📥 Commits

Reviewing files that changed from the base of the PR and between 2e7a732 and b52f74b.

📒 Files selected for processing (16)
  • common/constants.go
  • controller/option.go
  • controller/user.go
  • i18n/keys.go
  • i18n/locales/en.yaml
  • i18n/locales/zh-CN.yaml
  • i18n/locales/zh-TW.yaml
  • model/option.go
  • service/quota.go
  • web/src/components/settings/PersonalSetting.jsx
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json
✅ Files skipped from review due to trivial changes (9)
  • i18n/locales/zh-CN.yaml
  • i18n/locales/zh-TW.yaml
  • i18n/locales/en.yaml
  • i18n/keys.go
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/ru.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • controller/option.go
  • web/src/i18n/locales/zh-CN.json
  • web/src/components/settings/PersonalSetting.jsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/classic/src/components/settings/SystemSetting.jsx`:
- Around line 388-405: The loop over smsKeys currently prevents clearing saved
non-secret SMS fields because of the inputs[key] !== '' guard; adjust the logic
in the block that iterates smsKeys (the code using smsKeys, originInputs, and
inputs) to only skip empty values for masked/credential fields (the same ones
omitted by GetOptions) rather than for all keys—i.e., remove the global
inputs[key] !== '' check and instead check if the key is a credential/masked
field before skipping empty values; ensure you still handle SMSProvider deduping
(the existing SMSProvider existence check) and call updateOptions(options)
unchanged when options.length > 0.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab4f24ca-2c04-41ab-a6db-09786bdc3199

📥 Commits

Reviewing files that changed from the base of the PR and between b52f74b and 64a4395.

📒 Files selected for processing (5)
  • common/constants.go
  • controller/option.go
  • web/classic/src/components/settings/PersonalSetting.jsx
  • web/classic/src/components/settings/SystemSetting.jsx
  • web/classic/src/components/settings/personal/cards/NotificationSettings.jsx
✅ Files skipped from review due to trivial changes (1)
  • common/constants.go

Comment thread web/classic/src/components/settings/SystemSetting.jsx
cjy0123 added 2 commits April 29, 2026 17:43
- 腾讯云SMS:空SendStatusSet时返回错误而非静默成功
- 自定义SMS:HTTP URL不走Worker(Worker会拒绝非HTTPS),回退到直连
- 额度通知SMS:content保留可读文本供自定义HTTP接口的{{content}}占位符使用
- Classic前端submitSMS:区分凭证字段和配置字段,允许清空非凭证项

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
service/sms_notify.go (1)

396-423: ⚠️ Potential issue | 🟠 Major

Keep fetch-policy validation in the worker path.

useWorker now skips common.ValidateURLWithFetchSetting(...) entirely for HTTPS URLs, so enabling worker mode lets custom SMS requests bypass the same SSRF/domain/IP restrictions that the direct path enforces. Please validate finalURL before DoWorkerRequest, then dispatch through the worker only after it passes.

🛠️ Suggested fix
+ fetchSetting := system_setting.GetFetchSetting()
+ if err := common.ValidateURLWithFetchSetting(
+     finalURL,
+     fetchSetting.EnableSSRFProtection,
+     fetchSetting.AllowPrivateIp,
+     fetchSetting.DomainFilterMode,
+     fetchSetting.IpFilterMode,
+     fetchSetting.DomainList,
+     fetchSetting.IpList,
+     fetchSetting.AllowedPorts,
+     fetchSetting.ApplyIPFilterForDomain,
+ ); err != nil {
+     return fmt.Errorf("request reject: %v", err)
+ }
+
  useWorker := system_setting.EnableWorker() && strings.HasPrefix(strings.ToLower(finalURL), "https://")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/sms_notify.go` around lines 396 - 423, The worker path currently
skips SSRF/domain/IP validation, so before calling DoWorkerRequest when
useWorker is true, run common.ValidateURLWithFetchSetting(finalURL, ...) using
the same fetchSetting used in the non-worker branch (obtain via
system_setting.GetFetchSetting()), return the validation error if any, and only
then build the WorkerRequest and call DoWorkerRequest; update symbols
referenced: useWorker, finalURL, system_setting.GetFetchSetting(),
common.ValidateURLWithFetchSetting(...), DoWorkerRequest(), and WorkerRequest to
ensure parity with the non-worker path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@model/option.go`:
- Around line 392-401: The OptionMap is being written with the raw input before
validation so unsupported or blank methods can be exposed; update the
"SMSCustomMethod" branch to validate and normalize the input first (upper-case
and check against the exact allowed set used by service/sms_notify.go) and then
assign that normalized value into both common.SMSCustomMethod and
common.OptionMap[key] (use "POST" as the fallback for empty/unsupported values);
ensure the allowed-method list you check here exactly matches the sender
implementation in service/sms_notify.go so config cannot drift from runtime
behavior.

---

Duplicate comments:
In `@service/sms_notify.go`:
- Around line 396-423: The worker path currently skips SSRF/domain/IP
validation, so before calling DoWorkerRequest when useWorker is true, run
common.ValidateURLWithFetchSetting(finalURL, ...) using the same fetchSetting
used in the non-worker branch (obtain via system_setting.GetFetchSetting()),
return the validation error if any, and only then build the WorkerRequest and
call DoWorkerRequest; update symbols referenced: useWorker, finalURL,
system_setting.GetFetchSetting(), common.ValidateURLWithFetchSetting(...),
DoWorkerRequest(), and WorkerRequest to ensure parity with the non-worker path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b958e1d-9824-402f-a927-5ced96a9a8aa

📥 Commits

Reviewing files that changed from the base of the PR and between 64a4395 and 5ccf67c.

📒 Files selected for processing (4)
  • model/option.go
  • service/quota.go
  • service/sms_notify.go
  • web/classic/src/components/settings/SystemSetting.jsx

Comment thread model/option.go
Comment on lines +392 to +401
case "SMSCustomMethod":
upperMethod := strings.ToUpper(value)
switch upperMethod {
case "GET", "POST", "PUT", "DELETE", "PATCH":
common.SMSCustomMethod = upperMethod
case "":
// 空值保留默认的 POST
default:
common.SMSCustomMethod = "POST"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize SMSCustomMethod before storing it.

common.OptionMap[key] is written with the raw input before this branch runs, so a blank or unsupported method can still be exposed via /api/option/ even though common.SMSCustomMethod falls back to POST. This also accepts methods that service/sms_notify.go does not actually send, so the config can drift from runtime behavior.

🛠️ Suggested fix
 case "SMSCustomMethod":
     upperMethod := strings.ToUpper(value)
     switch upperMethod {
-    case "GET", "POST", "PUT", "DELETE", "PATCH":
+    case "GET", "POST":
         common.SMSCustomMethod = upperMethod
+        common.OptionMap[key] = upperMethod
     case "":
-        // 空值保留默认的 POST
+        common.SMSCustomMethod = "POST"
+        common.OptionMap[key] = "POST"
     default:
         common.SMSCustomMethod = "POST"
+        common.OptionMap[key] = "POST"
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model/option.go` around lines 392 - 401, The OptionMap is being written with
the raw input before validation so unsupported or blank methods can be exposed;
update the "SMSCustomMethod" branch to validate and normalize the input first
(upper-case and check against the exact allowed set used by
service/sms_notify.go) and then assign that normalized value into both
common.SMSCustomMethod and common.OptionMap[key] (use "POST" as the fallback for
empty/unsupported values); ensure the allowed-method list you check here exactly
matches the sender implementation in service/sms_notify.go so config cannot
drift from runtime behavior.

@Calcium-Ion
Calcium-Ion force-pushed the main branch 2 times, most recently from 51fdfc5 to 2b6f1df Compare August 30, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants