This commit is contained in:
ryan
2026-06-18 16:08:48 +08:00
parent 3366edb3a1
commit 772962c2e9
156 changed files with 23460 additions and 183 deletions
@@ -0,0 +1,251 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"fmt"
"strconv"
"strings"
ofauth "github.com/Rain-kl/Wavelet/internal/apps/openflare/auth"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/gin-gonic/gin"
)
type legacyUserPayload struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
DisplayName string `json:"display_name"`
Role int `json:"role"`
Email string `json:"email"`
}
type manageUserRequest struct {
Username string `json:"username"`
Action string `json:"action"`
}
type authSourcePayload struct {
Name string `json:"name"`
Type string `json:"type"`
DisplayName string `json:"display_name"`
IsActive bool `json:"is_active"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
OpenIDDiscoveryURL string `json:"openid_discovery_url"`
Scopes string `json:"scopes"`
IconURL string `json:"icon_url"`
}
type authSourceTogglePayload struct {
IsActive bool `json:"is_active"`
}
func GetAllUsers(c *gin.Context) {
page, _ := strconv.Atoi(c.Query("p"))
users, err := ofauth.ListUsers(c.Request.Context(), page)
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, users)
}
func SearchUsers(c *gin.Context) {
users, err := ofauth.SearchUsers(c.Request.Context(), c.Query("keyword"))
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, users)
}
func GetUser(c *gin.Context) {
id, ok := compat.IDParam(c)
if !ok {
return
}
user, err := ofauth.GetUserByID(c.Request.Context(), callerRole(c), uint64(id))
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, user)
}
func CreateUser(c *gin.Context) {
var req legacyUserPayload
if !compat.BindJSON(c, &req) {
return
}
if err := ofauth.CreateUser(c.Request.Context(), callerRole(c), ofauth.CreateUserInput{
Username: req.Username,
Password: req.Password,
DisplayName: req.DisplayName,
Role: req.Role,
Email: req.Email,
}); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
func UpdateUser(c *gin.Context) {
var req legacyUserPayload
if !compat.BindJSON(c, &req) {
return
}
if err := ofauth.UpdateUser(c.Request.Context(), callerRole(c), ofauth.UpdateUserInput{
ID: req.ID,
Username: req.Username,
Password: req.Password,
DisplayName: req.DisplayName,
Role: req.Role,
Email: req.Email,
}); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
func DeleteUser(c *gin.Context) {
id, ok := compat.IDParam(c)
if !ok {
return
}
if err := ofauth.DeleteUserByID(c.Request.Context(), callerRole(c), uint64(id)); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
func ManageUser(c *gin.Context) {
var req manageUserRequest
if !compat.BindJSON(c, &req) {
return
}
user, err := ofauth.ManageUser(c.Request.Context(), callerRole(c), ofauth.ManageUserInput{
Username: req.Username,
Action: req.Action,
})
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, user)
}
func ListAuthSources(c *gin.Context) {
sources, err := model.GetAuthSources(c.Request.Context())
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, sources)
}
func CreateAuthSource(c *gin.Context) {
var payload authSourcePayload
if !compat.BindJSON(c, &payload) {
return
}
source := payload.toModel()
if err := model.CreateAuthSource(c.Request.Context(), &source); err != nil {
compat.Fail(c, err.Error())
return
}
source.Sanitize()
compat.OK(c, source)
}
func UpdateAuthSource(c *gin.Context) {
id, err := parseAuthSourceID(c)
if err != nil {
compat.Fail(c, err.Error())
return
}
var payload authSourcePayload
if !compat.BindJSON(c, &payload) {
return
}
source := payload.toModel()
source.ID = id
keepSecret := strings.TrimSpace(source.ClientSecret) == ""
if err := model.UpdateAuthSource(c.Request.Context(), &source, keepSecret); err != nil {
compat.Fail(c, err.Error())
return
}
updated, err := model.GetAuthSourceByID(c.Request.Context(), id)
if err != nil {
compat.Fail(c, err.Error())
return
}
updated.Sanitize()
compat.OK(c, updated)
}
func DeleteAuthSource(c *gin.Context) {
id, err := parseAuthSourceID(c)
if err != nil {
compat.Fail(c, err.Error())
return
}
if err := model.DeleteAuthSource(c.Request.Context(), id); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
func ToggleAuthSource(c *gin.Context) {
id, err := parseAuthSourceID(c)
if err != nil {
compat.Fail(c, err.Error())
return
}
var payload authSourceTogglePayload
if !compat.BindJSON(c, &payload) {
return
}
if err := model.ToggleAuthSource(c.Request.Context(), id, payload.IsActive); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
func (payload authSourcePayload) toModel() model.AuthSource {
return model.AuthSource{
Name: payload.Name,
Type: payload.Type,
DisplayName: payload.DisplayName,
IsActive: payload.IsActive,
ClientID: payload.ClientID,
ClientSecret: payload.ClientSecret,
OpenIDDiscoveryURL: payload.OpenIDDiscoveryURL,
Scopes: payload.Scopes,
IconURL: payload.IconURL,
}
}
func parseAuthSourceID(c *gin.Context) (uint64, error) {
raw := strings.TrimSpace(c.Param("id"))
if raw == "" {
return 0, fmt.Errorf("认证源 ID 无效")
}
source, err := model.GetAuthSourceByName(c.Request.Context(), raw)
if err == nil {
return source.ID, nil
}
parsed, err := strconv.ParseUint(raw, 10, 64)
if err != nil || parsed == 0 {
return 0, fmt.Errorf("认证源 ID 无效")
}
return parsed, nil
}
@@ -0,0 +1,87 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"strconv"
"strings"
ofauth "github.com/Rain-kl/Wavelet/internal/apps/openflare/auth"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/gin-gonic/gin"
)
type linkExistingRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
// OAuthAuthorize starts OAuth authorization for a legacy auth source.
func OAuthAuthorize(c *gin.Context) {
url, err := ofauth.OAuthAuthorize(c.Request.Context(), c, c.Param("source"))
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, gin.H{"authorize_url": url})
}
// OAuthCallback handles the legacy GET OAuth callback.
func OAuthCallback(c *gin.Context) {
result, err := ofauth.OAuthCallback(c.Request.Context(), c, c.Param("source"))
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, result)
}
// LinkExistingOAuthAccount binds a pending OAuth account to an existing user.
func LinkExistingOAuthAccount(c *gin.Context) {
var req linkExistingRequest
if !compat.BindJSON(c, &req) {
return
}
result, err := ofauth.LinkExistingOAuthAccount(c.Request.Context(), c, ofauth.LinkExistingInput{
Username: req.Username,
Password: req.Password,
})
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, result)
}
// ListExternalAccounts returns external account bindings for the current user.
func ListExternalAccounts(c *gin.Context) {
userID := callerUserID(c)
accounts, err := model.ListExternalAccountsByUserID(c.Request.Context(), userID)
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, accounts)
}
// DeleteExternalAccount removes an external account binding.
func DeleteExternalAccount(c *gin.Context) {
userID := callerUserID(c)
if userID == 0 {
compat.Unauthorized(c, "无权进行此操作,未登录或 token 无效")
return
}
rawID := strings.TrimSpace(c.Param("id"))
id, err := strconv.ParseUint(rawID, 10, 64)
if err != nil || id == 0 {
compat.Fail(c, "绑定记录 ID 无效")
return
}
if err := model.DeleteExternalAccountForUser(c.Request.Context(), id, userID); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
@@ -0,0 +1,150 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
ofauth "github.com/Rain-kl/Wavelet/internal/apps/openflare/auth"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/gin-gonic/gin"
)
type loginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Code string `json:"code"`
}
type registerRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Nickname string `json:"nickname"`
DisplayName string `json:"display_name"`
Email string `json:"email"`
Code string `json:"code"`
}
type updateSelfRequest struct {
Username string `json:"username"`
Password string `json:"password"`
DisplayName string `json:"display_name"`
Email string `json:"email"`
}
type passwordResetRequest struct {
Email string `json:"email"`
Token string `json:"token"`
}
func Login(c *gin.Context) {
var req loginRequest
if !compat.BindJSON(c, &req) {
return
}
user, err := ofauth.Login(c.Request.Context(), c, ofauth.LoginInput{
Username: req.Username,
Password: req.Password,
Code: req.Code,
})
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, user)
}
func Logout(c *gin.Context) {
if err := ofauth.Logout(c.Request.Context(), c); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
func GetSelf(c *gin.Context) {
user, err := ofauth.GetSelf(c.Request.Context(), callerUserID(c))
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, user)
}
func UpdateSelf(c *gin.Context) {
var req updateSelfRequest
if !compat.BindJSON(c, &req) {
return
}
if err := ofauth.UpdateSelf(c.Request.Context(), callerUserID(c), ofauth.UpdateSelfInput{
Username: req.Username,
Password: req.Password,
DisplayName: req.DisplayName,
Email: req.Email,
}); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
func DeleteSelf(c *gin.Context) {
if err := ofauth.DeleteSelf(c.Request.Context(), callerUserID(c)); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
func GenerateToken(c *gin.Context) {
token, err := ofauth.GenerateUserToken(c.Request.Context(), callerUserID(c))
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, token)
}
func Register(c *gin.Context) {
var req registerRequest
if !compat.BindJSON(c, &req) {
return
}
user, err := ofauth.Register(c.Request.Context(), c, ofauth.RegisterInput{
Username: req.Username,
Password: req.Password,
Nickname: req.Nickname,
DisplayName: req.DisplayName,
Email: req.Email,
Code: req.Code,
})
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, user)
}
func SendEmailVerification(c *gin.Context) {
email := c.Query("email")
if err := ofauth.SendRegisterVerificationEmail(c.Request.Context(), email); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
func ResetPassword(c *gin.Context) {
var req passwordResetRequest
if !compat.BindJSON(c, &req) {
return
}
password, err := ofauth.ResetPassword(c.Request.Context(), ofauth.PasswordResetInput{
Email: req.Email,
Token: req.Token,
})
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, password)
}
@@ -0,0 +1,73 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"net/http"
"github.com/Rain-kl/Wavelet/internal/apps/cap"
"github.com/gin-gonic/gin"
)
type capRedeemRequest struct {
Token string `json:"token" binding:"required"`
Solutions []int `json:"solutions" binding:"required"`
}
// GetCapChallenge generates a CAP challenge for the legacy frontend.
func GetCapChallenge(c *gin.Context) {
scope := c.Param("scope")
if scope == "" {
scope = c.Query("scope")
}
if scope == "" {
scope = "login"
}
mgr := cap.GetDefaultManager()
resp, err := mgr.Generate(c.Request.Context(), scope)
if err != nil {
c.JSON(http.StatusInternalServerError, cap.RedeemResponse{
Success: false,
Error: err.Error(),
})
return
}
c.JSON(http.StatusOK, resp)
}
// RedeemCapChallenge redeems a CAP challenge for the legacy frontend.
func RedeemCapChallenge(c *gin.Context) {
scope := c.Param("scope")
if scope == "" {
scope = c.Query("scope")
}
if scope == "" {
scope = "login"
}
var req capRedeemRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, cap.RedeemResponse{
Success: false,
Error: "无效的参数",
})
return
}
mgr := cap.GetDefaultManager()
resp, err := mgr.Redeem(c.Request.Context(), req.Token, req.Solutions, scope)
if err != nil {
c.JSON(http.StatusInternalServerError, cap.RedeemResponse{
Success: false,
Error: err.Error(),
})
return
}
if !resp.Success {
c.JSON(http.StatusBadRequest, resp)
return
}
c.JSON(http.StatusOK, resp)
}
@@ -0,0 +1,57 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/cap"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/gin-gonic/gin"
)
// bridgeOpenFlareToken maps OpenFlare-Token to X-Access-Token for compat auth middleware.
func bridgeOpenFlareToken() gin.HandlerFunc {
return compat.BridgeOpenFlareToken()
}
// legacyCapAuth verifies PoW CAPTCHA for legacy login using OpenFlare response format.
func legacyCapAuth(scope string) gin.HandlerFunc {
mgr := cap.GetDefaultManager()
return func(c *gin.Context) {
if !cap.ProtectionEnabled(c.Request.Context()) {
c.Next()
return
}
token := c.GetHeader("X-Cap-Token")
if token == "" {
compat.Fail(c, "缺少人机验证凭证")
c.Abort()
return
}
valid, err := mgr.VerifyToken(c.Request.Context(), token, scope)
if err != nil || !valid {
compat.Fail(c, "人机验证凭证无效或已过期")
c.Abort()
return
}
c.Next()
}
}
func callerRole(c *gin.Context) int {
if role, ok := c.Get("of_role"); ok {
if v, ok := role.(int); ok {
return v
}
}
return 0
}
func callerUserID(c *gin.Context) uint64 {
if id, ok := c.Get("of_user_id"); ok {
if v, ok := id.(uint64); ok {
return v
}
}
return 0
}
@@ -0,0 +1,20 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
ofauth "github.com/Rain-kl/Wavelet/internal/apps/openflare/auth"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/gin-gonic/gin"
)
// SendPasswordResetEmail sends a password reset email for the legacy frontend.
func SendPasswordResetEmail(c *gin.Context) {
email := c.Query("email")
if err := ofauth.SendPasswordResetEmail(c.Request.Context(), email); err != nil {
compat.Fail(c, err.Error())
return
}
compat.OKMessage(c, "")
}
@@ -0,0 +1,25 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package legacy registers OpenFlare /api/* compatibility routes for the old frontend.
package legacy
import "github.com/gin-gonic/gin"
// RegisterRoutes mounts all OpenFlare legacy API routes under the /api group.
func RegisterRoutes(apiGroup *gin.RouterGroup) {
registerAuthRoutes(apiGroup)
registerOptionRoutes(apiGroup)
registerOriginRoutes(apiGroup)
registerApplyLogRoutes(apiGroup)
registerProxyRouteRoutes(apiGroup)
registerNodeRoutes(apiGroup)
registerWAFRoutes(apiGroup)
registerTLSRoutes(apiGroup)
registerConfigVersionRoutes(apiGroup)
registerAgentRoutes(apiGroup)
registerPagesRoutes(apiGroup)
registerRelayFlaredRoutes(apiGroup)
registerDashboardObsRoutes(apiGroup)
registerMiscRoutes(apiGroup)
}
@@ -0,0 +1,13 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/agent"
"github.com/gin-gonic/gin"
)
func registerAgentRoutes(apiGroup *gin.RouterGroup) {
agent.RegisterRoutes(apiGroup)
}
@@ -0,0 +1,19 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/apply_log"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/gin-gonic/gin"
)
func registerApplyLogRoutes(apiGroup *gin.RouterGroup) {
applyLogRoute := apiGroup.Group("/apply-logs")
applyLogRoute.Use(compat.AdminAuth())
{
applyLogRoute.GET("/", apply_log.GetApplyLogs)
applyLogRoute.POST("/cleanup", apply_log.CleanupApplyLogs)
}
}
@@ -0,0 +1,74 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/gin-gonic/gin"
)
func registerAuthRoutes(apiGroup *gin.RouterGroup) {
// /status, /notice, /about are registered by T-OPTION (option.RegisterRoutes).
apiGroup.GET("/verification", SendEmailVerification)
apiGroup.GET("/reset_password", SendPasswordResetEmail)
apiGroup.POST("/user/reset", ResetPassword)
oauthGroup := apiGroup.Group("/oauth")
{
oauthGroup.GET("/:source/authorize", OAuthAuthorize)
oauthGroup.GET("/:source/callback", OAuthCallback)
oauthGroup.POST("/link-existing", LinkExistingOAuthAccount)
externalAccounts := oauthGroup.Group("/external-accounts")
externalAccounts.Use(bridgeOpenFlareToken(), compat.UserAuth())
{
externalAccounts.GET("/", ListExternalAccounts)
externalAccounts.POST("/:id/delete", DeleteExternalAccount)
}
}
capGroup := apiGroup.Group("/cap")
{
capGroup.POST("/:scope/challenge", GetCapChallenge)
capGroup.POST("/:scope/redeem", RedeemCapChallenge)
}
userGroup := apiGroup.Group("/user")
{
userGroup.POST("/register", Register)
userGroup.POST("/login", legacyCapAuth("login"), Login)
userGroup.GET("/logout", Logout)
selfGroup := userGroup.Group("/")
selfGroup.Use(bridgeOpenFlareToken(), compat.UserAuth())
{
selfGroup.GET("/self", GetSelf)
selfGroup.POST("/self/update", UpdateSelf)
selfGroup.POST("/self/delete", DeleteSelf)
selfGroup.GET("/token", GenerateToken)
}
adminGroup := userGroup.Group("/")
adminGroup.Use(bridgeOpenFlareToken(), compat.AdminAuth())
{
adminGroup.GET("/", GetAllUsers)
adminGroup.GET("/search", SearchUsers)
adminGroup.GET("/:id", GetUser)
adminGroup.POST("/", CreateUser)
adminGroup.POST("/manage", ManageUser)
adminGroup.POST("/update", UpdateUser)
adminGroup.POST("/:id/delete", DeleteUser)
}
}
authSourceGroup := apiGroup.Group("/auth-sources")
authSourceGroup.Use(bridgeOpenFlareToken(), compat.RootAuth())
{
authSourceGroup.GET("/", ListAuthSources)
authSourceGroup.POST("/", CreateAuthSource)
authSourceGroup.POST("/:id/update", UpdateAuthSource)
authSourceGroup.POST("/:id/delete", DeleteAuthSource)
authSourceGroup.POST("/:id/toggle", ToggleAuthSource)
}
}
@@ -0,0 +1,25 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/config_version"
"github.com/gin-gonic/gin"
)
func registerConfigVersionRoutes(apiGroup *gin.RouterGroup) {
configVersionGroup := apiGroup.Group("/config-versions")
configVersionGroup.Use(compat.AdminAuth())
{
configVersionGroup.GET("/", config_version.ListConfigVersionsHandler)
configVersionGroup.GET("/active", config_version.GetActiveConfigVersionHandler)
configVersionGroup.GET("/preview", config_version.PreviewConfigVersionHandler)
configVersionGroup.GET("/diff", config_version.DiffConfigVersionHandler)
configVersionGroup.GET("/:id", config_version.GetConfigVersionHandler)
configVersionGroup.POST("/publish", config_version.PublishConfigVersionHandler)
configVersionGroup.POST("/:id/activate", config_version.ActivateConfigVersionHandler)
configVersionGroup.POST("/cleanup", config_version.CleanupConfigVersionsHandler)
}
}
@@ -0,0 +1,15 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/dashboard"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/observability"
"github.com/gin-gonic/gin"
)
func registerDashboardObsRoutes(apiGroup *gin.RouterGroup) {
dashboard.RegisterRoutes(apiGroup)
observability.RegisterRoutes(apiGroup)
}
@@ -0,0 +1,22 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/update"
"github.com/gin-gonic/gin"
)
func registerMiscRoutes(apiGroup *gin.RouterGroup) {
updateRoute := apiGroup.Group("/update")
updateRoute.Use(compat.RootAuth())
{
updateRoute.GET("/latest-release", update.GetLatestReleaseHandler)
updateRoute.GET("/logs/ws", update.StreamServerUpgradeLogsHandler)
updateRoute.POST("/manual-upload", update.UploadManualServerBinaryHandler)
updateRoute.POST("/manual-upgrade", update.ConfirmManualServerUpgradeHandler)
updateRoute.POST("/upgrade", update.UpgradeServerHandler)
}
}
@@ -0,0 +1,29 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/node"
"github.com/gin-gonic/gin"
)
func registerNodeRoutes(apiGroup *gin.RouterGroup) {
nodeRoute := apiGroup.Group("/nodes")
nodeRoute.Use(compat.AdminAuth())
{
nodeRoute.GET("/bootstrap-token", node.GetBootstrapTokenHandler)
nodeRoute.POST("/bootstrap-token/rotate", node.RotateBootstrapTokenHandler)
nodeRoute.GET("/", node.ListNodesHandler)
nodeRoute.POST("/", node.CreateNodeHandler)
nodeRoute.GET("/:id/agent-release", node.GetAgentReleaseHandler)
nodeRoute.POST("/:id/update", node.UpdateNodeHandler)
nodeRoute.POST("/:id/delete", node.DeleteNodeHandler)
nodeRoute.POST("/:id/agent-update", node.RequestAgentUpdateHandler)
nodeRoute.POST("/:id/openresty-restart", node.RequestOpenrestyRestartHandler)
nodeRoute.POST("/:id/force-sync", node.RequestForceSyncHandler)
nodeRoute.GET("/:id/observability", node.GetObservabilityHandler)
nodeRoute.POST("/:id/observability/cleanup", node.CleanupHealthEventsHandler)
}
}
@@ -0,0 +1,13 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/option"
"github.com/gin-gonic/gin"
)
func registerOptionRoutes(apiGroup *gin.RouterGroup) {
option.RegisterRoutes(apiGroup)
}
@@ -0,0 +1,22 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/origin"
"github.com/gin-gonic/gin"
)
func registerOriginRoutes(apiGroup *gin.RouterGroup) {
originRoute := apiGroup.Group("/origins")
originRoute.Use(compat.AdminAuth())
{
originRoute.GET("/", origin.GetOrigins)
originRoute.GET("/:id", origin.GetOrigin)
originRoute.POST("/", origin.CreateOriginHandler)
originRoute.POST("/:id/update", origin.UpdateOriginHandler)
originRoute.POST("/:id/delete", origin.DeleteOriginHandler)
}
}
@@ -0,0 +1,27 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/pages"
"github.com/gin-gonic/gin"
)
func registerPagesRoutes(apiGroup *gin.RouterGroup) {
pagesRoute := apiGroup.Group("/pages")
pagesRoute.Use(compat.AdminAuth())
{
pagesRoute.GET("/", pages.ListProjectsHandler)
pagesRoute.GET("/:id", pages.GetProjectHandler)
pagesRoute.POST("/", pages.CreateProjectHandler)
pagesRoute.POST("/:id/update", pages.UpdateProjectHandler)
pagesRoute.POST("/:id/delete", pages.DeleteProjectHandler)
pagesRoute.GET("/:id/deployments", pages.ListDeploymentsHandler)
pagesRoute.POST("/:id/deployments/upload", pages.UploadDeploymentHandler)
pagesRoute.POST("/:id/deployments/:deployment_id/activate", pages.ActivateDeploymentHandler)
pagesRoute.POST("/:id/deployments/:deployment_id/delete", pages.DeleteDeploymentHandler)
pagesRoute.GET("/deployments/:deployment_id/files", pages.ListDeploymentFilesHandler)
}
}
@@ -0,0 +1,22 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/proxy_route"
"github.com/gin-gonic/gin"
)
func registerProxyRouteRoutes(apiGroup *gin.RouterGroup) {
proxyRouteGroup := apiGroup.Group("/proxy-routes")
proxyRouteGroup.Use(compat.AdminAuth())
{
proxyRouteGroup.GET("/", proxy_route.GetProxyRoutes)
proxyRouteGroup.GET("/:id", proxy_route.GetProxyRouteHandler)
proxyRouteGroup.POST("/", proxy_route.CreateProxyRouteHandler)
proxyRouteGroup.POST("/:id/update", proxy_route.UpdateProxyRouteHandler)
proxyRouteGroup.POST("/:id/delete", proxy_route.DeleteProxyRouteHandler)
}
}
@@ -0,0 +1,28 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/flared"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/relay"
"github.com/gin-gonic/gin"
)
func registerRelayFlaredRoutes(apiGroup *gin.RouterGroup) {
relayRoute := apiGroup.Group("/relay")
relayRoute.Use(relay.RelayAuth())
{
relayRoute.POST("/heartbeat", relay.PostHeartbeat)
relayRoute.GET("/ws", relay.GetWebSocket)
}
flaredRoute := apiGroup.Group("/flared")
flaredRoute.Use(flared.TunnelAuth())
{
flaredRoute.POST("/heartbeat", flared.PostHeartbeat)
flaredRoute.GET("/config/active", flared.GetActiveConfig)
flaredRoute.POST("/apply-log", flared.PostApplyLog)
flaredRoute.GET("/ws", flared.GetWebSocket)
}
}
@@ -0,0 +1,53 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/tls"
"github.com/gin-gonic/gin"
)
func registerTLSRoutes(apiGroup *gin.RouterGroup) {
managedDomainRoute := apiGroup.Group("/managed-domains")
managedDomainRoute.Use(compat.AdminAuth())
{
managedDomainRoute.GET("/", tls.GetManagedDomains)
managedDomainRoute.GET("/match", tls.MatchManagedDomainCertificateHandler)
managedDomainRoute.POST("/", tls.CreateManagedDomainHandler)
managedDomainRoute.POST("/:id/update", tls.UpdateManagedDomainHandler)
managedDomainRoute.POST("/:id/delete", tls.DeleteManagedDomainHandler)
}
tlsCertificateRoute := apiGroup.Group("/tls-certificates")
tlsCertificateRoute.Use(compat.AdminAuth())
{
tlsCertificateRoute.GET("/", tls.GetCertificates)
tlsCertificateRoute.GET("/:id", tls.GetCertificateDetail)
tlsCertificateRoute.GET("/:id/content", tls.GetCertificateContentHandler)
tlsCertificateRoute.POST("/", tls.CreateCertificateHandler)
tlsCertificateRoute.POST("/:id/update", tls.UpdateCertificateHandler)
tlsCertificateRoute.POST("/:id/update-acme", tls.UpdateACMECertificateHandler)
tlsCertificateRoute.POST("/:id/convert-acme", tls.ConvertCertificateToACMEHandler)
tlsCertificateRoute.POST("/import-file", tls.ImportCertificateFile)
tlsCertificateRoute.POST("/:id/delete", tls.DeleteCertificateHandler)
tlsCertificateRoute.POST("/apply", tls.ApplyCertificateHandler)
tlsCertificateRoute.POST("/:id/renew", tls.RenewCertificateHandler)
}
acmeAccountRoute := apiGroup.Group("/acme-accounts")
acmeAccountRoute.Use(compat.AdminAuth())
{
acmeAccountRoute.GET("/default", tls.GetDefaultAcmeAccountHandler)
}
dnsAccountRoute := apiGroup.Group("/dns-accounts")
dnsAccountRoute.Use(compat.AdminAuth())
{
dnsAccountRoute.GET("/", tls.GetDNSAccounts)
dnsAccountRoute.POST("/", tls.CreateDNSAccountHandler)
dnsAccountRoute.POST("/:id/update", tls.UpdateDNSAccountHandler)
dnsAccountRoute.POST("/:id/delete", tls.DeleteDNSAccountHandler)
}
}
@@ -0,0 +1,34 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/waf"
"github.com/gin-gonic/gin"
)
func registerWAFRoutes(apiGroup *gin.RouterGroup) {
wafRoute := apiGroup.Group("/waf")
wafRoute.Use(compat.AdminAuth())
{
wafRoute.GET("/ip-groups", waf.ListIPGroupsHandler)
wafRoute.GET("/ip-groups/:id", waf.GetIPGroupHandler)
wafRoute.POST("/ip-groups", waf.CreateIPGroupHandler)
wafRoute.POST("/ip-groups/test", waf.TestIPGroupAutoConfigHandler)
wafRoute.POST("/ip-groups/:id/update", waf.UpdateIPGroupHandler)
wafRoute.POST("/ip-groups/:id/delete", waf.DeleteIPGroupHandler)
wafRoute.POST("/ip-groups/:id/sync", waf.SyncIPGroupHandler)
wafRoute.GET("/rule-groups", waf.ListRuleGroupsHandler)
wafRoute.GET("/rule-groups/:id", waf.GetRuleGroupHandler)
wafRoute.POST("/rule-groups", waf.CreateRuleGroupHandler)
wafRoute.POST("/rule-groups/:id/update", waf.UpdateRuleGroupHandler)
wafRoute.POST("/rule-groups/:id/delete", waf.DeleteRuleGroupHandler)
wafRoute.POST("/rule-groups/:id/sites", waf.ReplaceRuleGroupSitesHandler)
wafRoute.GET("/sites/:route_id/rule-groups", waf.GetSiteRuleGroupsHandler)
wafRoute.POST("/sites/:route_id/rule-groups", waf.ReplaceSiteRuleGroupsHandler)
}
}
@@ -0,0 +1,30 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package legacy
import (
ofauth "github.com/Rain-kl/Wavelet/internal/apps/openflare/auth"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/compat"
"github.com/gin-gonic/gin"
)
// GetStatus returns public server status for the legacy frontend.
func GetStatus(c *gin.Context) {
data, err := ofauth.BuildPublicStatus(c.Request.Context())
if err != nil {
compat.Fail(c, err.Error())
return
}
compat.OK(c, data)
}
// GetNotice returns the legacy notice content.
func GetNotice(c *gin.Context) {
compat.OK(c, ofauth.GetNotice(c.Request.Context()))
}
// GetAbout returns the legacy about content.
func GetAbout(c *gin.Context) {
compat.OK(c, ofauth.GetAbout(c.Request.Context()))
}