Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions SOLECA794/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module awesomeProject

go 1.23.1

require (
github.com/gin-gonic/gin v1.10.0
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.12
)

require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
316 changes: 316 additions & 0 deletions SOLECA794/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
package main

import (
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"io"
"net/http"
"strconv"
"time"
)

type User struct {
ID int
Name string
Pass string
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security Issue: Storing Passwords in Plain Text

The Pass field in the User struct stores passwords as plain text, which poses a significant security risk. Passwords should be hashed using a strong hashing algorithm like bcrypt before being stored.

Apply this diff to hash passwords:

 type User struct {
     ID     int
     Name   string
-    Pass   string
+    Pass   []byte // Store hashed passwords
     Email  string
     Identy int
 }

In your signup and reset handlers, hash the password before saving:

+import "golang.org/x/crypto/bcrypt"

 // In the signup handler
-password := c.PostForm("password")
+password := c.PostForm("password")
+hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+if err != nil {
+    c.HTML(http.StatusInternalServerError, "signup.tmpl", gin.H{
+        "message": "Internal Server Error",
+    })
+    return
+}

-userSign = User{Name: username, Pass: password, Email: email, Identy: identity}
+userSign = User{Name: username, Pass: hashedPassword, Email: email, Identy: identity}

In your login handler, compare hashed passwords:

 // In the login handler
-password := c.PostForm("password")
+inputPassword := c.PostForm("password")

 // After retrieving userLog from the database
-if password != userLog.Pass {
+if err := bcrypt.CompareHashAndPassword(userLog.Pass, []byte(inputPassword)); err != nil {
     c.HTML(http.StatusOK, "index.tmpl", gin.H{
         "message": "Incorrect password",
         "v":       1,
     })
     return
 }

Committable suggestion was skipped due to low confidence.

Email string
Identy int
Comment on lines +17 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Typo in Struct Field: 'Identy' Should Be 'Identity'

The field Identy in the User struct appears to be a typo. It should be Identity to accurately represent its purpose.

Apply this diff to correct the typo:

 type User struct {
     ID     int
     Name   string
     Pass   string
     Email  string
-    Identy int
+    Identity int
 }

Ensure to update all occurrences of Identy in your codebase to Identity.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Pass string
Email string
Identy int
Pass string
Email string
Identity int

}
Comment on lines +14 to +20
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security Issue: Storing Passwords in Plain Text

The User struct's Pass field stores user passwords as plain text, which poses a significant security risk. Passwords should never be stored in plain text. Instead, they should be hashed using a strong hashing algorithm like bcrypt before being stored in the database.

To address this, you can modify the User struct and update the password handling in your code:

 type User struct {
 	ID     int
 	Name   string
-	Pass   string
+	Pass   []byte // Store hashed passwords
 	Email  string
-	Identy int
+	Identity int
 }

In your signup handler, hash the password before saving the user:

 // In the /signup POST handler
 password := c.PostForm("password")
+hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+if err != nil {
+    c.HTML(http.StatusInternalServerError, "signup.tmpl", gin.H{
+        "error": "Internal Server Error",
+    })
+    return
+}
 
 // When creating the user
-userSign = User{Name: username, Pass: password, Email: email, Identy: identity}
+userSign = User{Name: username, Pass: hashedPassword, Email: email, Identity: identity}

In your login handler, compare the hashed password:

 // In the /login POST handler
-password := c.PostForm("password")
+inputPassword := c.PostForm("password")
 
 // After retrieving userLog from the database
-if password != userLog.Pass {
+if err := bcrypt.CompareHashAndPassword(userLog.Pass, []byte(inputPassword)); err != nil {
     // Passwords do not match
 }

Don't forget to import the bcrypt package:

import "golang.org/x/crypto/bcrypt"

type Question struct {
ID int
Content string
From string
Time time.Time
}

var UserNow User
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Concurrency Issue: Global Variable UserNow

Using the global variable UserNow to track the currently logged-in user is unsafe in a web application due to concurrency issues. Multiple users accessing the application simultaneously can overwrite UserNow, leading to incorrect behavior and potential security breaches.

Instead, implement session management to track user sessions securely. You can use a session middleware like gin-contrib/sessions:

import (
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
)

// In your main function, set up the session store
func main() {
    r := gin.Default()
    store := cookie.NewStore([]byte("secret"))
    r.Use(sessions.Sessions("session", store))
    // ...
}

Then, in your handlers, manage the session data:

// In login handler
session := sessions.Default(c)
session.Set("user_id", userLog.ID)
session.Save()
// In logout handler
session := sessions.Default(c)
session.Clear()
session.Save()


func IsLog(c *gin.Context) {
if UserNow.Name == "" {
c.Redirect(http.StatusFound, "/")
c.Abort()
} else {
c.Next()
}
}
Comment on lines +37 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Concurrency Issue: Global Variable UserNow

Using a global variable UserNow to track the currently logged-in user is unsafe in a web application due to concurrency issues. Multiple users accessing the application simultaneously can overwrite UserNow, leading to incorrect behavior.

Consider implementing session management to handle user authentication securely. You can use middleware like gin-contrib/sessions:

import (
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
)

// In your main function
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("session", store))

Update your handlers to use sessions:

 // In login handler
-session := sessions.Default(c)
-session.Set("user_id", userLog.ID)
-session.Save()

+session := sessions.Default(c)
+session.Set("user_id", userLog.ID)
+session.Save()

Modify the IsLog middleware:

 func IsLog(c *gin.Context) {
-    if UserNow.Name == "" {
+    session := sessions.Default(c)
+    userID := session.Get("user_id")
+    if userID == nil {
         c.Redirect(http.StatusFound, "/")
         c.Abort()
     } else {
+        // Retrieve user information if needed
         c.Next()
     }
 }

func main() {
r := gin.Default()

dsn := "root:794ASMIN@soleca@tcp(127.0.0.1:3306)/my_database?charset=utf8mb4&parseTime=True&loc=Local"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security Issue: Hardcoded Database Credentials

The database credentials are hardcoded in the dsn variable, which is insecure and can lead to credential leakage. It's best practice to use environment variables or a configuration file to manage sensitive information.

You can modify the code to read from environment variables:

-import (
+import (
     "fmt"
     "github.com/gin-gonic/gin"
     "gorm.io/driver/mysql"
     "gorm.io/gorm"
     "io"
     "net/http"
     "strconv"
     "time"
+    "os"
 )
 
 func main() {
-    dsn := "root:794ASMIN@soleca@tcp(127.0.0.1:3306)/my_database?charset=utf8mb4&parseTime=True&loc=Local"
+    dbUser := os.Getenv("DB_USER")
+    dbPassword := os.Getenv("DB_PASSWORD")
+    dbHost := os.Getenv("DB_HOST")
+    dbName := os.Getenv("DB_NAME")
+    dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
+        dbUser, dbPassword, dbHost, dbName)

Ensure you set the environment variables DB_USER, DB_PASSWORD, DB_HOST, and DB_NAME in your deployment environment.

Committable suggestion was skipped due to low confidence.

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("connecting filed !!!")
}
db.AutoMigrate(&User{}, &Question{})
//重置自增主键
//db.Exec("ALTER TABLE users AUTO_INCREMENT = 1")

//查询某一个用户的方法
//a := db.Where("name = ? ", "SOLECA").First(&User{})
//if a.Error == gorm.ErrRecordNotFound {
// u0 := User{Name: "SOLECA", Pass: "123123", Identy: 0}
// db.Create(&u0)
//}

r.LoadHTMLGlob("templates/*")
r.GET("/", func(c *gin.Context) {
UserNow.Name = ""
c.HTML(http.StatusOK, "index.tmpl", nil)
})
r.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
var userLog User
err := db.Where("name = ? ", username).First(&userLog).Error
if err == gorm.ErrRecordNotFound {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"message": "登录失败!你还没有注册",
"message2": "没有用户?现在注册一个▼ ▼ ▼",
"v": 0,
})

} else {
if password != userLog.Pass {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"message": "您输入的密码不正确",
"v": 1,
})

} else {
UserNow = userLog
c.Redirect(http.StatusFound, "/home")
}
}
})
Comment on lines +60 to +84
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security Issue: Plaintext Password Verification

In the login handler, passwords are compared in plaintext, which is insecure and assumes passwords are stored in plaintext.

After hashing passwords during signup, update the login handler to verify hashed passwords:

 // In the /login POST handler
-password := c.PostForm("password")
+inputPassword := c.PostForm("password")
 
 // After retrieving userLog from the database
-if password != userLog.Pass {
+if err := bcrypt.CompareHashAndPassword(userLog.Pass, []byte(inputPassword)); err != nil {
     c.HTML(http.StatusOK, "index.tmpl", gin.H{
         "message": "Incorrect password",
         "v":       1,
     })
     return
 }

Committable suggestion was skipped due to low confidence.

r.GET("/signup", func(c *gin.Context) {
c.HTML(http.StatusOK, "signup.tmpl", gin.H{})
})
r.POST("/signup", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
email := c.PostForm("email")
submission := c.PostForm("submission")
var userSign User
err := db.Where("name = ? ", username).First(&userSign).Error
if err == gorm.ErrRecordNotFound {
if submission == "123321" {
userSign = User{Name: username, Pass: password, Email: email, Identy: 0}
Comment on lines +97 to +98
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security Issue: Hardcoded Admin Access Code

The admin access code "123321" is hardcoded in the signup logic, which poses a security risk. Anyone with access to the code can register as an admin.

Consider implementing a secure method for admin registration, such as:

  • Removing public admin registration entirely.
  • Implementing an invitation system.
  • Verifying admin status through a secure, out-of-band process.

Update the code to reflect this change:

- if submission == "123321" {
+ if isValidAdminSubmission(submission) {
     // Admin registration logic
 } else {
     // Regular user registration
 }

Define the isValidAdminSubmission function to handle admin verification securely.

Committable suggestion was skipped due to low confidence.

db.Create(&userSign)
//c.SetCookie("message", "管理员注册成功!", 3600, "/", "localhost", false, true)
c.Redirect(http.StatusFound, "/")
} else {
userSign = User{Name: username, Pass: password, Email: email, Identy: 1}
db.Create(&userSign)

c.Redirect(http.StatusFound, "/")
}
} else {
c.HTML(http.StatusOK, "signup.tmpl", gin.H{
"text": "用户名重复!请重试",
})
}
})
r.GET("/reset", func(c *gin.Context) {

})

//----------------------------------------------------------------------------

r.GET("/home", IsLog, func(c *gin.Context) {
if UserNow.Name == "" {
c.Redirect(http.StatusFound, "/")
}
v := UserNow.Identy
var questions []Question
err := db.Find(&questions).Error
if err != nil {
fmt.Println("this")
c.JSON(http.StatusBadRequest, gin.H{"error": "NO QUESTIONS"})
}
c.HTML(http.StatusOK, "home.tmpl", gin.H{
"Name": UserNow.Name,
"V": v,
"K": 1,
"questions": questions,
})
})
home := r.Group("/home")
{
home.POST("/new", IsLog, func(c *gin.Context) {
new1 := c.PostForm("new")
var NewQuestion Question = Question{Content: new1, From: UserNow.Name, Time: time.Now()}
err := db.Create(&NewQuestion).Error
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "failed"})
}
c.Redirect(http.StatusFound, "/home/all")
})
Comment on lines +168 to +175
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Error Handling: Missing Error Response

In the /home/new route, if an error occurs while creating a new question, the user receives a JSON error response, but the successful path redirects to a different route.

For consistency, consider providing user-friendly HTML responses for errors or redirecting appropriately.

 if err != nil {
-    c.JSON(http.StatusBadRequest, gin.H{"error": "failed"})
+    c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
+        "message": "Failed to create a new question.",
+    })
     return
 }
 c.Redirect(http.StatusFound, "/home/all")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
new1 := c.PostForm("new")
var NewQuestion Question = Question{Content: new1, From: UserNow.Name, Time: time.Now()}
err := db.Create(&NewQuestion).Error
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "failed"})
}
c.Redirect(http.StatusFound, "/home/all")
})
new1 := c.PostForm("new")
var NewQuestion Question = Question{Content: new1, From: UserNow.Name, Time: time.Now()}
err := db.Create(&NewQuestion).Error
if err != nil {
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
"message": "Failed to create a new question.",
})
return
}
c.Redirect(http.StatusFound, "/home/all")
})

home.GET("/manage", IsLog, func(c *gin.Context) {
if UserNow.Identy == 0 {
fmt.Println("ok")
v := UserNow.Identy
var questionss []Question
err := db.Find(&questionss).Error
if err != nil {
fmt.Println("this")
c.JSON(http.StatusBadRequest, gin.H{"error": "NO QUESTIONS"})
}
c.HTML(http.StatusOK, "home.tmpl", gin.H{
"Name": UserNow.Name,
"V": v,
"K": 0,
"questions": questionss,
})
}
})
home.GET("/all", IsLog, func(c *gin.Context) {
v := UserNow.Identy
var questions []Question
err := db.Find(&questions).Error
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "NO QUESTIONS"})
}
c.HTML(http.StatusOK, "home.tmpl", gin.H{
Comment on lines +225 to +229
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle Database Errors Appropriately

The error handling for db.Find(&questions) may not be accurate. If no records are found, GORM does not return an error; it returns an empty slice.

Adjust the error handling to catch actual database errors:

 err := db.Find(&questions).Error
 if err != nil {
-    c.JSON(http.StatusBadRequest, gin.H{"error": "NO QUESTIONS"})
+    c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
+        "message": "Database error occurred.",
+    })
+    return
 }
+// Optionally, handle the case where 'questions' is empty in your template

Committable suggestion was skipped due to low confidence.

"Name": UserNow.Name,
"V": v,
"K": 1,
"questions": questions,
})
})
home.GET("/my", IsLog, func(c *gin.Context) {
var questions []Question
db.Where("`from` = ?", UserNow.Name).Find(&questions)
//if err != nil {
// c.JSON(http.StatusBadRequest, gin.H{"error": "NO QUESTIONS"})
//}
c.HTML(http.StatusOK, "showmy.tmpl", gin.H{
"questions": questions,
})

Comment on lines +238 to +246
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Error Handling: Commented-Out Error Check

In the /home/my route, the error check is commented out. If an error occurs, it won't be handled.

Uncomment and update the error handling logic:

 db.Where("`from` = ?", UserNow.Name).Find(&questions)
-if err != nil {
-    c.JSON(http.StatusBadRequest, gin.H{"error": "NO QUESTIONS"})
-}
+if err := db.Where("`from` = ?", UserNow.Name).Find(&questions).Error; err != nil {
+    c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
+        "message": "Failed to retrieve your questions.",
+    })
+    return
+}

Committable suggestion was skipped due to low confidence.

})
home.POST("home/:id/answer", IsLog, func(c *gin.Context) {

})
home.DELETE("/delete", IsLog, func(c *gin.Context) {
body, _ := io.ReadAll(c.Request.Body)
// 将字符串转换为数值
receive, _ := strconv.Atoi(string(body))
db.Where("id = ?", receive).Delete(&Question{})
})
Comment on lines +307 to +311
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security Issue: Reading Request Body Directly

Directly reading the request body using io.ReadAll in a DELETE request can lead to issues. It's better to extract parameters properly.

Modify the handler to parse form data or URL parameters:

-home.DELETE("/question/delete", IsLog, func(c *gin.Context) {
-    body, _ := io.ReadAll(c.Request.Body)
-    receive, _ := strconv.Atoi(string(body))
+home.POST("/question/delete", IsLog, func(c *gin.Context) {
+    id := c.PostForm("id")
+    receive, err := strconv.Atoi(id)
+    if err != nil {
+        c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{
+            "message": "Invalid question ID.",
+        })
+        return
+    }

     db.Where("id = ?", receive).Delete(&Question{})
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
body, _ := io.ReadAll(c.Request.Body)
// 将字符串转换为数值
receive, _ := strconv.Atoi(string(body))
db.Where("id = ?", receive).Delete(&Question{})
})
home.POST("/question/delete", IsLog, func(c *gin.Context) {
id := c.PostForm("id")
receive, err := strconv.Atoi(id)
if err != nil {
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{
"message": "Invalid question ID.",
})
return
}
db.Where("id = ?", receive).Delete(&Question{})
})

}
r.Run(":8080")
}

/*

c.Request.URL.Path="/b"
r.HandleContext(c)




//cookie重定向
c.SetCookie("message", "注册成功!", 3600, "/", "", false, true)
c.Redirect(http.StatusFound, "/")
router.GET("/", func(c *gin.Context) {
// 获取 cookie 中的消息
message, err := c.Cookie("message")
if err != nil {
message = ""
}

c.HTML(http.StatusOK, "hello.tmpl", gin.H{
"message1": message,
})
})




package controllers

import (
"net/http"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"myapp/models"
"golang.org/x/crypto/bcrypt"
)

var db *gorm.DB

func init() {
var err error
db, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
}

func ShowLoginPage(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", gin.H{
"title": "Login",
})
}

func PerformLogin(c *gin.Context) {
var user models.User
username := c.PostForm("username")
password := c.PostForm("password")

if err := db.Where("username = ?", username).First(&user).Error; err != nil {
c.HTML(http.StatusUnauthorized, "login.html", gin.H{
"ErrorTitle": "Login Failed",
"ErrorMessage": "Invalid credentials provided",
})
return
}

if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
c.HTML(http.StatusUnauthorized, "login.html", gin.H{
"ErrorTitle": "Login Failed",
"ErrorMessage": "Invalid credentials provided",
})
return
}

c.HTML(http.StatusOK, "login.html", gin.H{
"title": "Login Successful",
})
}




<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .title }}</title>
</head>
<body>
<h1>{{ .title }}</h1>
{{ if .ErrorTitle }}
<h2>{{ .ErrorTitle }}</h2>
<p>{{ .ErrorMessage }}</p>
{{ end }}
<form action="/login" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<br>
<button type="submit">Login</button>
</form>
</body>
</html>





*/
Loading