package auth import ( "net/http" "net/url" "nixcn-cms/data" "nixcn-cms/internal/crypto/jwt" "nixcn-cms/pkgs/magiclink" "nixcn-cms/pkgs/turnstile" log "github.com/sirupsen/logrus" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) type MagicLinkRequest struct { Email string `json:"email" binding:"required,email"` TurnstileToken string `json:"turnstile_token" binding:"required"` } func RequestMagicLink(c *gin.Context) { // Parse request var req MagicLinkRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": "invalid request"}) return } // Cloudflare turnstile ok, err := turnstile.VerifyTurnstile(req.TurnstileToken, c.ClientIP()) if err != nil || !ok { c.JSON(403, gin.H{"error": "turnstile failed"}) return } // Generate magic token token, err := magiclink.NewMagicToken(req.Email) if err != nil { c.JSON(500, gin.H{"error": "internal error"}) return } link, err := url.JoinPath(viper.GetString("server.external_url"), "/api/v1/auth/magic/verify?token="+token) if err != nil { log.Error("Magic link join failed!") c.JSON(500, gin.H{"message": "magic link join failed"}) return } // TODO send email c.JSON(200, gin.H{"message": "magic link sent", "magic_link": link}) } func VerifyMagicLink(c *gin.Context) { // Get token from url token := c.Query("token") if token == "" { c.JSON(400, gin.H{"error": "missing token"}) return } // Verify email token email, ok := magiclink.VerifyMagicToken(token) if !ok { c.JSON(401, gin.H{"error": "invalid or expired token"}) return } // Generate jwt userInfo := new(data.User) err := userInfo.GetByEmail(email) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"status": "user not found"}) } jwtToken, _ := jwt.GenerateToken(userInfo.UserId, "application") c.JSON(200, gin.H{ "jwt_token": jwtToken, "email": email, }) }