diff --git a/openapi/user/entry.go b/openapi/user/entry.go index f5aa7609..4c379c8a 100644 --- a/openapi/user/entry.go +++ b/openapi/user/entry.go @@ -561,6 +561,80 @@ func validatePassword(password string) error { return nil } +// GinSendOTP handles resending OTP verification code +func GinSendOTP(c *gin.Context) { + // Get authorized info from the temporary token + authInfo := oauth.GetAuthorizedInfo(c) + if authInfo == nil || authInfo.Scope != ScopeEntryVerification { + errorResp := &response.ErrorResponse{ + Code: response.ErrAccessDenied.Code, + ErrorDescription: "Invalid or missing entry verification token", + } + response.RespondWithError(c, response.StatusUnauthorized, errorResp) + return + } + + // Get username and username_type from token claims + username, _ := c.Get("__username") + usernameType, _ := c.Get("__username_type") + + usernameStr, ok := username.(string) + if !ok || usernameStr == "" { + errorResp := &response.ErrorResponse{ + Code: response.ErrInvalidRequest.Code, + ErrorDescription: "Username not found in token", + } + response.RespondWithError(c, response.StatusBadRequest, errorResp) + return + } + + usernameTypeStr, ok := usernameType.(string) + if !ok || usernameTypeStr == "" { + errorResp := &response.ErrorResponse{ + Code: response.ErrInvalidRequest.Code, + ErrorDescription: "Username type not found in token", + } + response.RespondWithError(c, response.StatusBadRequest, errorResp) + return + } + + // Get locale from query parameter + locale := c.Query("locale") + + // Get entry configuration + config := GetEntryConfig(locale) + if config == nil { + errorResp := &response.ErrorResponse{ + Code: response.ErrInvalidRequest.Code, + ErrorDescription: "Entry configuration not found", + } + response.RespondWithError(c, response.StatusNotFound, errorResp) + return + } + + // Generate new OTP + otpID, verificationCode := generateEntryOTP() + + // Send verification message asynchronously + go func() { + ctx := context.Background() + err := sendVerificationMessage(ctx, config, usernameTypeStr, usernameStr, verificationCode, locale) + if err != nil { + log.Error("Failed to resend verification code to %s: %v", usernameStr, err) + return + } + log.Info("Verification code resent to %s (OTP ID: %s)", usernameStr, otpID) + }() + + // Prepare response + otpResponse := EntrySendOTPResponse{ + OtpID: otpID, + ExpiresIn: 600, // 10 minutes + } + + response.RespondWithSuccess(c, response.StatusOK, otpResponse) +} + // GinEntryRegister handles user registration func GinEntryRegister(c *gin.Context) { // Get authorized info from the temporary token diff --git a/openapi/user/types.go b/openapi/user/types.go index 0b0e6da4..e86c0b6b 100644 --- a/openapi/user/types.go +++ b/openapi/user/types.go @@ -290,6 +290,12 @@ type EntryLoginRequest struct { Locale string `json:"locale,omitempty"` } +// EntrySendOTPResponse represents the response for sending OTP verification code +type EntrySendOTPResponse struct { + OtpID string `json:"otp_id"` // OTP ID for verification + ExpiresIn int `json:"expires_in,omitempty"` // OTP expiration in seconds +} + // Built-in preset mapping types const ( MappingGoogle = "google" diff --git a/openapi/user/user.go b/openapi/user/user.go index 9dbc0305..5f3254e6 100644 --- a/openapi/user/user.go +++ b/openapi/user/user.go @@ -35,6 +35,7 @@ func Attach(group *gin.RouterGroup, oauth types.OAuth) { // Register a new user group.POST("/entry/register", oauth.Guard, GinEntryRegister) // Register a new user group.POST("/entry/login", oauth.Guard, GinEntryLogin) // Login a user + group.POST("/entry/otp", oauth.Guard, GinSendOTP) // Send OTP group.POST("/logout", oauth.Guard, placeholder) // User logout // Logined User Settings