add registration link logic and registration html
This commit is contained in:
parent
22cbc94da6
commit
0af10cd17e
17 changed files with 291 additions and 10 deletions
2
go.mod
2
go.mod
|
@ -13,6 +13,7 @@ require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/glebarez/sqlite v1.10.0 // indirect
|
github.com/glebarez/sqlite v1.10.0 // indirect
|
||||||
|
github.com/go-mail/mail v2.3.1+incompatible // indirect
|
||||||
github.com/google/uuid v1.4.0 // indirect
|
github.com/google/uuid v1.4.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
@ -27,6 +28,7 @@ require (
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
golang.org/x/crypto v0.16.0 // indirect
|
golang.org/x/crypto v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.15.0 // indirect
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
modernc.org/libc v1.22.5 // indirect
|
modernc.org/libc v1.22.5 // indirect
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
modernc.org/memory v1.5.0 // indirect
|
modernc.org/memory v1.5.0 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -7,6 +7,8 @@ github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9g
|
||||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||||
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
|
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
|
||||||
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
|
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
|
||||||
|
github.com/go-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM=
|
||||||
|
github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIINUkSmuKOiLIDkWbL6M=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ=
|
github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ=
|
||||||
github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U=
|
github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U=
|
||||||
|
@ -48,6 +50,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||||
|
|
|
@ -25,9 +25,9 @@ func InitializeDB() error {
|
||||||
|
|
||||||
models.DB = db
|
models.DB = db
|
||||||
|
|
||||||
err = models.DB.AutoMigrate(&models.User{})
|
err = models.DB.AutoMigrate(&models.User{}, &models.EmailConfirmation{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Can't do DB Migration", "Model", "User")
|
slog.Error("Can't do DB Migration")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,10 +57,13 @@ func SetupFiber() error {
|
||||||
//fiberapp.Static("/admin/", "./web")
|
//fiberapp.Static("/admin/", "./web")
|
||||||
fiberapp.Add("POST", "/api/users/register", handlers.POSTUserRegister)
|
fiberapp.Add("POST", "/api/users/register", handlers.POSTUserRegister)
|
||||||
fiberapp.Add("POST", "/api/users/login", handlers.POSTUserLogin)
|
fiberapp.Add("POST", "/api/users/login", handlers.POSTUserLogin)
|
||||||
|
fiberapp.Add("GET", "/confirm/email", handlers.GETUserEmailConfirm)
|
||||||
|
|
||||||
v1 := fiberapp.Group("/api/v1")
|
v1 := fiberapp.Group("/api/v1")
|
||||||
v1.Use(cors.New(cors.Config{AllowOrigins: "*"}))
|
v1.Use(cors.New(cors.Config{AllowOrigins: "*"}))
|
||||||
|
|
||||||
|
fiberapp.Static("/", "./static")
|
||||||
|
|
||||||
listenerr := fiberapp.Listen(ip + ":" + port)
|
listenerr := fiberapp.Listen(ip + ":" + port)
|
||||||
if listenerr != nil {
|
if listenerr != nil {
|
||||||
return listenerr
|
return listenerr
|
||||||
|
|
6
internal/communication/classes.go
Normal file
6
internal/communication/classes.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package communication
|
||||||
|
|
||||||
|
type registrationTemplateVars struct {
|
||||||
|
BaseUrl string
|
||||||
|
RegistrationSecret string
|
||||||
|
}
|
53
internal/communication/mail.go
Normal file
53
internal/communication/mail.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package communication
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.lila.network/lauralani/tlm-login-server/internal/config"
|
||||||
|
"code.lila.network/lauralani/tlm-login-server/internal/models"
|
||||||
|
"crypto/tls"
|
||||||
|
"github.com/go-mail/mail"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SendRegistrationNotification(user models.User, registrationsecet string) error {
|
||||||
|
message := mail.NewMessage()
|
||||||
|
|
||||||
|
message.SetHeader("From", config.SMTPFrom)
|
||||||
|
message.SetHeader("To", user.Email)
|
||||||
|
message.SetHeader("Subject", "Your Registration for TLM Nation")
|
||||||
|
|
||||||
|
var data = registrationTemplateVars{
|
||||||
|
BaseUrl: config.BasePath,
|
||||||
|
RegistrationSecret: registrationsecet,
|
||||||
|
}
|
||||||
|
|
||||||
|
plainstring := new(strings.Builder)
|
||||||
|
texttemplate, err := template.New("registration.plain.tmpl").ParseFiles("internal/communication/templates/registration.plain.tmpl")
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Can't load template", "Template", "internal/communication/templates/registration.plain.tmpl")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = texttemplate.Execute(plainstring, data)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Can't execute template", "Template", "internal/communication/templates/registration.plain.tmpl")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
message.SetBody("text/plain", plainstring.String())
|
||||||
|
|
||||||
|
slog.Debug("got requested to send mail:", "FROM", config.SMTPFrom, "TO", user.Email)
|
||||||
|
|
||||||
|
mailer := mail.NewDialer(config.SMTPServer, config.SMTPPort, config.SMTPUser, config.SMTPPassword)
|
||||||
|
if !config.SMTPVerifySSL {
|
||||||
|
mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mailer.DialAndSend(message)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
11
internal/communication/templates/registration.plain.tmpl
Normal file
11
internal/communication/templates/registration.plain.tmpl
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Thank you for your rgeistration for TLM Nation!
|
||||||
|
|
||||||
|
There is only one more step until you can start competing and posting Highscores:
|
||||||
|
|
||||||
|
Please confirm your email address by clicking on the following link:
|
||||||
|
{{ .BaseUrl }}/confirm/email?cs={{ .RegistrationSecret }}
|
||||||
|
|
||||||
|
This link is valid for four hours!
|
||||||
|
|
||||||
|
Thank you so much~
|
||||||
|
Laura
|
|
@ -26,6 +26,24 @@ func InitializeConfig() error {
|
||||||
return errors.New("required env var is empty: TLM_SMTP_SERVER")
|
return errors.New("required env var is empty: TLM_SMTP_SERVER")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if os.Getenv("TLM_SMTP_USER") != "" {
|
||||||
|
SMTPUser = os.Getenv("TLM_SMTP_USER")
|
||||||
|
} else {
|
||||||
|
return errors.New("required env var is empty: TLM_SMTP_USER")
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("TLM_SMTP_PASSWORD") != "" {
|
||||||
|
SMTPPassword = os.Getenv("TLM_SMTP_PASSWORD")
|
||||||
|
} else {
|
||||||
|
return errors.New("required env var is empty: TLM_SMTP_PASSWORD")
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("TLM_BASEPATH") != "" {
|
||||||
|
BasePath = os.Getenv("TLM_BASEPATH")
|
||||||
|
} else {
|
||||||
|
return errors.New("required env var is empty: TLM_BASEPATH")
|
||||||
|
}
|
||||||
|
|
||||||
if os.Getenv("TLM_SMTP_VERIFYSSL") != "" {
|
if os.Getenv("TLM_SMTP_VERIFYSSL") != "" {
|
||||||
var err error
|
var err error
|
||||||
SMTPVerifySSL, err = strconv.ParseBool(os.Getenv("TLM_SMTP_VERIFYSSL"))
|
SMTPVerifySSL, err = strconv.ParseBool(os.Getenv("TLM_SMTP_VERIFYSSL"))
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
var SMTPPort int
|
var SMTPPort int
|
||||||
var SMTPHost string
|
|
||||||
var SMTPFrom string
|
var SMTPFrom string
|
||||||
var SMTPServer string
|
var SMTPServer string
|
||||||
|
var SMTPUser string
|
||||||
|
var SMTPPassword string
|
||||||
var SMTPVerifySSL bool
|
var SMTPVerifySSL bool
|
||||||
|
|
||||||
var SQLitePath string
|
var SQLitePath string
|
||||||
|
|
||||||
|
// BasePath is the http path without trailing slash
|
||||||
|
var BasePath string
|
||||||
|
|
23
internal/handlers/user-email-confirm.go
Normal file
23
internal/handlers/user-email-confirm.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.lila.network/lauralani/tlm-login-server/internal/models"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GETUserEmailConfirm(c *fiber.Ctx) error {
|
||||||
|
confirmationsecret := c.Query("cs")
|
||||||
|
|
||||||
|
if confirmationsecret == "" {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request")
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmation := models.EmailConfirmation{ConfirmationCode: confirmationsecret}
|
||||||
|
|
||||||
|
models.DB.First(&confirmation)
|
||||||
|
|
||||||
|
//fmt.Printf("UserID of Confirmation: %v\n", confirmation.UserID)
|
||||||
|
//fmt.Printf("Username of Confirmation: %v\n", confirmation.User.Username)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"code.lila.network/lauralani/tlm-login-server/internal/communication"
|
||||||
"code.lila.network/lauralani/tlm-login-server/internal/models"
|
"code.lila.network/lauralani/tlm-login-server/internal/models"
|
||||||
"code.lila.network/lauralani/tlm-login-server/internal/shared"
|
"code.lila.network/lauralani/tlm-login-server/internal/shared"
|
||||||
"encoding/json"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"log"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func POSTUserRegister(c *fiber.Ctx) error {
|
func POSTUserRegister(c *fiber.Ctx) error {
|
||||||
|
@ -14,9 +14,8 @@ func POSTUserRegister(c *fiber.Ctx) error {
|
||||||
var user models.User
|
var user models.User
|
||||||
var queryuser models.User
|
var queryuser models.User
|
||||||
|
|
||||||
err := json.Unmarshal(c.Body(), ®istration)
|
err := c.BodyParser(®istration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request")
|
return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +27,7 @@ func POSTUserRegister(c *fiber.Ctx) error {
|
||||||
Message: "Username is already taken!",
|
Message: "Username is already taken!",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error(err.Error())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -40,6 +39,48 @@ func POSTUserRegister(c *fiber.Ctx) error {
|
||||||
dbop := models.DB.Create(&user)
|
dbop := models.DB.Create(&user)
|
||||||
if dbop.Error != nil {
|
if dbop.Error != nil {
|
||||||
slog.Error("Error creating user", "Username", registration.Username, "Error", err.Error())
|
slog.Error("Error creating user", "Username", registration.Username, "Error", err.Error())
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmation := models.EmailConfirmation{
|
||||||
|
User: user,
|
||||||
|
ConfirmationCode: shared.RandomString(64),
|
||||||
|
ExpiresAt: time.Now().Add(time.Hour * 4),
|
||||||
|
}
|
||||||
|
|
||||||
|
dbop = models.DB.Create(&confirmation)
|
||||||
|
if dbop.Error != nil {
|
||||||
|
slog.Error("Error creating EmailConfirmation", "Username", registration.Username, "Error", err.Error())
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
emailerr := communication.SendRegistrationNotification(user, confirmation.ConfirmationCode)
|
||||||
|
if emailerr != nil {
|
||||||
|
dbdelete := models.DB.Delete(&user)
|
||||||
|
if dbdelete.Error != nil {
|
||||||
|
slog.Error("Error deleting user", "Username", registration.Username, "Error", err.Error())
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
dbdelete = models.DB.Delete(&confirmation)
|
||||||
|
if dbdelete.Error != nil {
|
||||||
|
slog.Error("Error deleting confirmation", "Username", registration.Username, "Error", err.Error())
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := models.ErrorReply{
|
||||||
|
Status: "error",
|
||||||
|
Message: "Invalid email",
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Error("Error sending mail", "Error", emailerr.Error())
|
||||||
|
|
||||||
|
c.Status(fiber.StatusBadRequest)
|
||||||
|
err = c.JSON(reply)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpreply models.RegistrationReply
|
var httpreply models.RegistrationReply
|
||||||
|
@ -51,7 +92,7 @@ func POSTUserRegister(c *fiber.Ctx) error {
|
||||||
c.Status(fiber.StatusCreated)
|
c.Status(fiber.StatusCreated)
|
||||||
err = c.JSON(httpreply)
|
err = c.JSON(httpreply)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -14,3 +14,11 @@ type User struct {
|
||||||
PasswordHash []byte `gorm:"size:60"`
|
PasswordHash []byte `gorm:"size:60"`
|
||||||
LastLoginAt time.Time
|
LastLoginAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EmailConfirmation struct {
|
||||||
|
gorm.Model
|
||||||
|
UserID uint
|
||||||
|
User User
|
||||||
|
ConfirmationCode string
|
||||||
|
ExpiresAt time.Time
|
||||||
|
}
|
||||||
|
|
30
internal/shared/random-string.go
Normal file
30
internal/shared/random-string.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package shared
|
||||||
|
|
||||||
|
import "math/rand"
|
||||||
|
|
||||||
|
// see https://stackoverflow.com/a/31832326
|
||||||
|
|
||||||
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
const (
|
||||||
|
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||||
|
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||||
|
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||||
|
)
|
||||||
|
|
||||||
|
func RandomString(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
|
||||||
|
for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
|
||||||
|
if remain == 0 {
|
||||||
|
cache, remain = rand.Int63(), letterIdxMax
|
||||||
|
}
|
||||||
|
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||||
|
b[i] = letterBytes[idx]
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
cache >>= letterIdxBits
|
||||||
|
remain--
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b)
|
||||||
|
}
|
5
static/css/pico.min.css
vendored
Normal file
5
static/css/pico.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/css/pico.min.css.map
Normal file
1
static/css/pico.min.css.map
Normal file
File diff suppressed because one or more lines are too long
59
static/index.html
Normal file
59
static/index.html
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>register</title>
|
||||||
|
<link rel="stylesheet" href="css/pico.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="container">
|
||||||
|
<h1>TLM Nation - User Registration</h1>
|
||||||
|
<form action="/api/users/register" method="post">
|
||||||
|
<label for="username">
|
||||||
|
Username (a-zA-Z0-9-_):
|
||||||
|
<input type="text" name="username" id="username" placeholder="Username" aria-label="Username"
|
||||||
|
required />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label for="email">
|
||||||
|
Email:
|
||||||
|
<input type="email" name="email" id="email" placeholder="Email address"
|
||||||
|
aria-label="Email address" required />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<fieldset>
|
||||||
|
<label for="password">
|
||||||
|
Password:
|
||||||
|
<input onchange="ValidatePasswordFields()" type="password" name="password" id="password" placeholder="********"
|
||||||
|
aria-label="Password" required />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<label for="password-repeat">
|
||||||
|
Repeat Password:
|
||||||
|
<input onchange="ValidatePasswordFields()" type="password" id="password-repeat" placeholder="********"
|
||||||
|
aria-label="Repeat Password" required />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<fieldset>
|
||||||
|
<label for="terms">
|
||||||
|
<input type="checkbox" role="switch" id="terms" name="terms"/>
|
||||||
|
I agree to the <a href="#" onclick="event.preventDefault()">Privacy Policy</a>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<button type="submit" id="submit">Register</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="js/register.js" defer></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
14
static/js/register.js
Normal file
14
static/js/register.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
function ValidatePasswordFields() {
|
||||||
|
var password = document.getElementById("password");
|
||||||
|
var passwordrepeat = document.getElementById("password-repeat");
|
||||||
|
|
||||||
|
if (password.value === passwordrepeat.value) {
|
||||||
|
passwordrepeat.setCustomValidity("");
|
||||||
|
console.log("passwords match")
|
||||||
|
} else {
|
||||||
|
passwordrepeat.setCustomValidity("Passwords don't match!");
|
||||||
|
console.log("passwords mööp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue