补齐手机动态码版本接口并自动核对前端路由

This commit is contained in:
smail 2026-09-18 22:21:29 +08:00
parent a47f50c04e
commit f9d1a858d1
2 changed files with 88 additions and 0 deletions

View File

@ -32,6 +32,7 @@ func registerV1Routes(mux *http.ServeMux) {
registerV1MethodRoute(mux, "/api/v1/security/recovery/verify", "/api/security/recovery/verify", http.MethodPost, 16<<10, controllers.VerifyPasswordRecovery)
registerV1MethodRoute(mux, "/api/v1/security/recovery/complete", "/api/security/recovery/complete", http.MethodPost, 16<<10, controllers.CompletePasswordRecovery)
registerV1CredentialsQuestionRoute(mux)
registerV1TOTPRoute(mux)
registerV1MethodRoute(mux, "/api/v1/security/credentials/recovery-codes", "/api/security/credentials/recovery-codes", http.MethodGet, 0, controllers.GetRecoveryCodeStatus)
registerV1MethodRoute(mux, "/api/v1/security/credentials/recovery-codes/generate", "/api/security/credentials/recovery-codes/generate", http.MethodPost, 16<<10, controllers.GenerateRecoveryCodes)
registerV1MethodRoute(mux, "/api/v1/security/credentials/recovery-codes/revoke", "/api/security/credentials/recovery-codes/revoke", http.MethodPost, 4<<10, controllers.RevokeRecoveryCodes)
@ -133,6 +134,19 @@ func registerV1CredentialsQuestionRoute(mux *http.ServeMux) {
})
}
func registerV1TOTPRoute(mux *http.ServeMux) {
handler := limitBody(4<<10, contextIterceptor(rewriteV1Path("/api/security/totp", controllers.TOTPSettings)))
mux.HandleFunc("/api/v1/security/totp", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet, http.MethodPost:
handler.ServeHTTP(w, r)
default:
w.Header().Set("Allow", "GET, POST")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
})
}
func registerV1PluginRoutes(mux *http.ServeMux) {
getUserSettings := contextIterceptor(controllers.GetTelegramUserSettings)
updateUserSettings := limitBody(16<<10, contextIterceptor(controllers.UpdateTelegramUserSettings))

View File

@ -1,8 +1,12 @@
package http_server
import (
"io/fs"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"regexp"
"testing"
"git.pcux.cn/xuans/Smail/config"
@ -201,6 +205,7 @@ func TestFrontendV1RoutesAreRegistered(t *testing.T) {
"/api/v1/security/login/resend",
"/api/v1/security/login/status",
"/api/v1/security/login/verify",
"/api/v1/security/totp",
"/api/v1/security/recovery/begin",
"/api/v1/security/recovery/complete",
"/api/v1/security/recovery/verify",
@ -235,3 +240,72 @@ func TestFrontendV1RoutesAreRegistered(t *testing.T) {
}
}
}
func TestFrontendSourceV1RoutesAreRegistered(t *testing.T) {
mux := http.NewServeMux()
setupMux := http.NewServeMux()
registerSetupAPIRoutes(setupMux, withSetupSecurity(t, "0123456789abcdefghijklmnop"), func(http.ResponseWriter, *http.Request) {})
if err := router(mux); err != nil {
t.Fatal(err)
}
pattern := regexp.MustCompile("[\x22\x27\x60](/api/v1/[a-zA-Z0-9_/-]+)[\x22\x27\x60]")
count := 0
err := filepath.WalkDir("../../../frontend/src", func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if entry.IsDir() || (filepath.Ext(path) != ".js" && filepath.Ext(path) != ".vue" && filepath.Ext(path) != ".mjs") {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return err
}
for _, match := range pattern.FindAllSubmatch(content, -1) {
endpoint := string(match[1])
_, route := mux.Handler(httptest.NewRequest(http.MethodGet, endpoint, nil))
if endpoint == "/api/v1/setup" {
_, route = setupMux.Handler(httptest.NewRequest(http.MethodGet, endpoint, nil))
}
if route == "" || route == "/" {
t.Errorf("%s: endpoint %s is not registered", path, endpoint)
}
count++
}
return nil
})
if err != nil {
t.Fatal(err)
}
if count == 0 {
t.Fatal("no frontend API calls inspected")
}
}
func TestV1TOTPRouteMethods(t *testing.T) {
oldInit := config.IsInit
t.Cleanup(func() { config.SetInitState(oldInit) })
config.SetInitState(false)
mux := http.NewServeMux()
registerV1TOTPRoute(mux)
for _, method := range []string{http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodPut} {
recorder := httptest.NewRecorder()
mux.ServeHTTP(recorder, httptest.NewRequest(method, "/api/v1/security/totp", nil))
want := http.StatusOK
if method == http.MethodDelete || method == http.MethodPut {
want = http.StatusMethodNotAllowed
if recorder.Header().Get("Allow") != "GET, POST" {
t.Fatal("incorrect allowed methods")
}
}
if recorder.Code != want {
t.Errorf("%s returned %d, want %d", method, recorder.Code, want)
}
if recorder.Header().Get("Deprecation") != "" {
t.Fatal("versioned TOTP route marked as legacy")
}
}
if publicSecurityPath("/api/v1/security/totp") {
t.Fatal("TOTP settings must require a session")
}
}