package handlers import ( "errors" "net/url" "strings" ) // LoginRequest POST /api/auth/login JSON body type LoginRequest struct { Account string `json:"account"` Password string `json:"password"` ClientID string `json:"clientId"` ClientName string `json:"clientName"` TurnstileToken string `json:"turnstileToken"` } // VerifyRequest POST /api/auth/verify type VerifyRequest struct { Token string `json:"token"` } // RegisterRequest POST /api/auth/register type RegisterRequest struct { Account string `json:"account"` Password string `json:"password"` Username string `json:"username"` Email string `json:"email"` InviteCode string `json:"inviteCode"` TurnstileToken string `json:"turnstileToken"` } // VerifyEmailRequest POST /api/auth/verify-email type VerifyEmailRequest struct { Account string `json:"account"` Code string `json:"code"` } // UpdateProfileRequest PUT /api/auth/profile type UpdateProfileRequest struct { Password *string `json:"password"` Username *string `json:"username"` Phone *string `json:"phone"` AvatarURL *string `json:"avatarUrl"` WebsiteURL *string `json:"websiteUrl"` Bio *string `json:"bio"` } // ForgotPasswordRequest POST /api/auth/forgot-password type ForgotPasswordRequest struct { Account string `json:"account"` Email string `json:"email"` } // ResetPasswordRequest POST /api/auth/reset-password type ResetPasswordRequest struct { Account string `json:"account"` Code string `json:"code"` NewPassword string `json:"newPassword"` } // SecondaryEmailRequest POST /api/auth/secondary-email/request type SecondaryEmailRequest struct { Email string `json:"email"` } // VerifySecondaryEmailRequest POST /api/auth/secondary-email/verify type VerifySecondaryEmailRequest struct { Email string `json:"email"` Code string `json:"code"` } // UpdateCheckInConfigRequest PUT /api/admin/check-in/config type UpdateCheckInConfigRequest struct { RewardCoins int `json:"rewardCoins"` } // UpdateRegistrationPolicyRequest PUT /api/admin/registration type UpdateRegistrationPolicyRequest struct { RequireInviteCode bool `json:"requireInviteCode"` ForbiddenAccounts string `json:"forbiddenAccounts"` InviteRegisterRewardCoins *int `json:"inviteRegisterRewardCoins"` // nil 表示不修改此项 } // CreateInviteRequest POST /api/admin/registration/invites type CreateInviteRequest struct { Note string `json:"note"` MaxUses int `json:"maxUses"` ExpiresAt string `json:"expiresAt"` } // CreateUserRequest POST /api/admin/users type CreateUserRequest struct { Account string `json:"account"` Password string `json:"password"` Username string `json:"username"` Email string `json:"email"` Level int `json:"level"` SproutCoins int `json:"sproutCoins"` SecondaryEmails []string `json:"secondaryEmails"` Phone string `json:"phone"` AvatarURL string `json:"avatarUrl"` WebsiteURL string `json:"websiteUrl"` Bio string `json:"bio"` } const maxBanReasonLen = 500 // UpdateUserRequest PUT /api/admin/users/:account type UpdateUserRequest struct { Password *string `json:"password"` Username *string `json:"username"` Email *string `json:"email"` Level *int `json:"level"` SproutCoins *int `json:"sproutCoins"` SecondaryEmails *[]string `json:"secondaryEmails"` Phone *string `json:"phone"` AvatarURL *string `json:"avatarUrl"` WebsiteURL *string `json:"websiteUrl"` Bio *string `json:"bio"` Banned *bool `json:"banned"` BanReason *string `json:"banReason"` } // OAuthBindURLRequest POST /api/auth/oauth/:provider/bind type OAuthBindURLRequest struct { ReturnTo string `json:"returnTo"` } // PutAdminOAuthRequest PUT /api/admin/oauth type PutAdminOAuthRequest struct { GitHubEnabled bool `json:"githubEnabled"` GiteaEnabled bool `json:"giteaEnabled"` GoogleEnabled bool `json:"googleEnabled"` MicrosoftEnabled bool `json:"microsoftEnabled"` LinuxdoEnabled bool `json:"linuxdoEnabled"` GitHubClientID string `json:"githubClientId"` GitHubClientSecret string `json:"githubClientSecret"` GiteaBaseURL string `json:"giteaBaseUrl"` GiteaClientID string `json:"giteaClientId"` GiteaClientSecret string `json:"giteaClientSecret"` GoogleClientID string `json:"googleClientId"` GoogleClientSecret string `json:"googleClientSecret"` MicrosoftClientID string `json:"microsoftClientId"` MicrosoftClientSecret string `json:"microsoftClientSecret"` MicrosoftTenant string `json:"microsoftTenant"` LinuxdoConnectBaseURL string `json:"linuxdoConnectBaseUrl"` LinuxdoClientID string `json:"linuxdoClientId"` LinuxdoClientSecret string `json:"linuxdoClientSecret"` GitHubLogoURL string `json:"githubLogoUrl"` GiteaLogoURL string `json:"giteaLogoUrl"` GoogleLogoURL string `json:"googleLogoUrl"` MicrosoftLogoURL string `json:"microsoftLogoUrl"` LinuxdoLogoURL string `json:"linuxdoLogoUrl"` AllowedReturnPrefixes []string `json:"allowedReturnPrefixes"` AllowOAuthSignUpWhenInviteRequired bool `json:"allowOAuthSignUpWhenInviteRequired"` } // PutAdminTurnstileRequest PUT /api/admin/turnstile type PutAdminTurnstileRequest struct { Enabled bool `json:"enabled"` SiteKey string `json:"siteKey"` SecretKey string `json:"secretKey"` } const maxWebsiteURLLen = 2048 func normalizePublicWebsiteURL(raw string) (string, error) { s := strings.TrimSpace(raw) if s == "" { return "", nil } if len(s) > maxWebsiteURLLen { return "", errors.New("website url is too long") } lower := strings.ToLower(s) if strings.HasPrefix(lower, "javascript:") || strings.HasPrefix(lower, "data:") { return "", errors.New("invalid website url") } candidate := s if !strings.Contains(candidate, "://") { candidate = "https://" + candidate } u, err := url.Parse(candidate) if err != nil || u.Host == "" { return "", errors.New("invalid website url") } scheme := strings.ToLower(u.Scheme) if scheme != "http" && scheme != "https" { return "", errors.New("only http and https urls are allowed") } u.Scheme = scheme return u.String(), nil }