cleanup and addes auth.signup event

This commit is contained in:
anthdm 2024-06-21 15:02:28 +02:00
parent 2e732a8c79
commit 9e389f1d28
10 changed files with 113 additions and 79 deletions

View file

@ -15,5 +15,5 @@ import (
// Register your events here. // Register your events here.
func RegisterEvents() { func RegisterEvents() {
event.Subscribe("foo.bar", events.HandleFooEvent) event.Subscribe("auth.signup", events.HandleUserSignup)
} }

View file

@ -1,6 +0,0 @@
package events
import "context"
// Event handlers
func HandleFooEvent(ctx context.Context, event any) {}

View file

@ -0,0 +1,16 @@
package events
import (
"AABBCCDD/plugins/auth"
"context"
"fmt"
)
// Event handlers
func HandleUserSignup(ctx context.Context, event any) {
userWithToken, ok := event.(auth.UserWithVerificationToken)
if !ok {
return
}
fmt.Printf("user signup: %v\n", userWithToken)
}

View file

@ -22,9 +22,16 @@ func InitializeMiddleware(router *chi.Mux) {
// Define your routes in here // Define your routes in here
func InitializeRoutes(router *chi.Mux) { func InitializeRoutes(router *chi.Mux) {
// Authentication plugin: // Authentication plugin
//
// By default the auth plugin is active. To disable the auth plugin
// you will need to pass your own handler in the `AuthFunc`` field
// of the `kit.AuthenticationConfig`.
// authConfig := kit.AuthenticationConfig{
// AuthFunc: YourAuthHandler,
// RedirectURL: "/login",
// }
auth.InitializeRoutes(router) auth.InitializeRoutes(router)
authConfig := kit.AuthenticationConfig{ authConfig := kit.AuthenticationConfig{
AuthFunc: auth.AuthenticateUser, AuthFunc: auth.AuthenticateUser,
RedirectURL: "/login", RedirectURL: "/login",

View file

@ -3,15 +3,12 @@ package auth
import ( import (
"AABBCCDD/app/db" "AABBCCDD/app/db"
"database/sql" "database/sql"
"fmt"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"time" "time"
"github.com/anthdm/superkit/event"
"github.com/anthdm/superkit/kit" "github.com/anthdm/superkit/kit"
"github.com/anthdm/superkit/mail"
v "github.com/anthdm/superkit/validate" v "github.com/anthdm/superkit/validate"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/google/uuid" "github.com/google/uuid"
@ -27,27 +24,15 @@ var authSchema = v.Schema{
"password": v.Rules(v.Required), "password": v.Rules(v.Required),
} }
var signupSchema = v.Schema{ func HandleLoginIndex(kit *kit.Kit) error {
"email": v.Rules(v.Email),
"password": v.Rules(
v.ContainsSpecial,
v.ContainsUpper,
v.Min(7),
v.Max(50),
),
"firstName": v.Rules(v.Min(2), v.Max(50)),
"lastName": v.Rules(v.Min(2), v.Max(50)),
}
func HandleAuthIndex(kit *kit.Kit) error {
if kit.Auth().Check() { if kit.Auth().Check() {
redirectURL := kit.Getenv("SUPERKIT_AUTH_REDIRECT_AFTER_LOGIN", "/profile") redirectURL := kit.Getenv("SUPERKIT_AUTH_REDIRECT_AFTER_LOGIN", "/profile")
return kit.Redirect(http.StatusSeeOther, redirectURL) return kit.Redirect(http.StatusSeeOther, redirectURL)
} }
return kit.Render(AuthIndex(AuthIndexPageData{})) return kit.Render(LoginIndex(LoginIndexPageData{}))
} }
func HandleAuthCreate(kit *kit.Kit) error { func HandleLoginCreate(kit *kit.Kit) error {
var values LoginFormValues var values LoginFormValues
errors, ok := v.Request(kit.Request, &values, authSchema) errors, ok := v.Request(kit.Request, &values, authSchema)
if !ok { if !ok {
@ -109,7 +94,7 @@ func HandleAuthCreate(kit *kit.Kit) error {
return kit.Redirect(http.StatusSeeOther, redirectURL) return kit.Redirect(http.StatusSeeOther, redirectURL)
} }
func HandleAuthDelete(kit *kit.Kit) error { func HandleLoginDelete(kit *kit.Kit) error {
sess := kit.GetSession(userSessionName) sess := kit.GetSession(userSessionName)
defer func() { defer func() {
sess.Values = map[any]any{} sess.Values = map[any]any{}
@ -125,52 +110,6 @@ func HandleAuthDelete(kit *kit.Kit) error {
return kit.Redirect(http.StatusSeeOther, "/") return kit.Redirect(http.StatusSeeOther, "/")
} }
func HandleSignupIndex(kit *kit.Kit) error {
return kit.Render(SignupIndex(SignupIndexPageData{}))
}
func HandleSignupCreate(kit *kit.Kit) error {
var values SignupFormValues
errors, ok := v.Request(kit.Request, &values, signupSchema)
if !ok {
return kit.Render(SignupForm(values, errors))
}
if values.Password != values.PasswordConfirm {
errors.Add("passwordConfirm", "passwords do not match")
return kit.Render(SignupForm(values, errors))
}
user, err := createUserFromFormValues(values)
if err != nil {
return err
}
token, err := createVerificationToken(user.ID)
if err != nil {
return err
}
event.Emit("user.signup", user)
mail.NOOPMailer{}.SendEmail(kit.Request.Context(), mail.Contents{
Title: token,
})
return kit.Render(ConfirmEmail(user.Email))
}
func createVerificationToken(userID int) (string, error) {
expiryStr := kit.Getenv("SUPERKIT_AUTH_EMAIL_VERIFICATION_EXPIRY_IN_HOURS", "1")
expiry, err := strconv.Atoi(expiryStr)
if err != nil {
expiry = 1
}
claims := jwt.RegisteredClaims{
Subject: fmt.Sprint(userID),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * time.Duration(expiry))),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(os.Getenv("SUPERKIT_SECRET")))
}
func HandleEmailVerify(kit *kit.Kit) error { func HandleEmailVerify(kit *kit.Kit) error {
tokenStr := kit.Request.URL.Query().Get("token") tokenStr := kit.Request.URL.Query().Get("token")
if len(tokenStr) == 0 { if len(tokenStr) == 0 {

View file

@ -7,7 +7,7 @@ import (
"AABBCCDD/app/views/components" "AABBCCDD/app/views/components"
) )
type AuthIndexPageData struct { type LoginIndexPageData struct {
FormValues LoginFormValues FormValues LoginFormValues
FormErrors v.Errors FormErrors v.Errors
} }
@ -17,7 +17,7 @@ type LoginFormValues struct {
Password string `form:"password"` Password string `form:"password"`
} }
templ AuthIndex(data AuthIndexPageData) { templ LoginIndex(data LoginIndexPageData) {
@layouts.BaseLayout() { @layouts.BaseLayout() {
<div class="fixed top-6 right-6"> <div class="fixed top-6 right-6">
@components.ThemeSwitcher() @components.ThemeSwitcher()

View file

@ -15,9 +15,9 @@ func InitializeRoutes(router chi.Router) {
router.Group(func(auth chi.Router) { router.Group(func(auth chi.Router) {
auth.Use(kit.WithAuthentication(authConfig, false)) auth.Use(kit.WithAuthentication(authConfig, false))
auth.Get("/login", kit.Handler(HandleAuthIndex)) auth.Get("/login", kit.Handler(HandleLoginIndex))
auth.Post("/login", kit.Handler(HandleAuthCreate)) auth.Post("/login", kit.Handler(HandleLoginCreate))
auth.Delete("/logout", kit.Handler(HandleAuthDelete)) auth.Delete("/logout", kit.Handler(HandleLoginDelete))
auth.Get("/signup", kit.Handler(HandleSignupIndex)) auth.Get("/signup", kit.Handler(HandleSignupIndex))
auth.Post("/signup", kit.Handler(HandleSignupCreate)) auth.Post("/signup", kit.Handler(HandleSignupCreate))

View file

@ -0,0 +1,71 @@
package auth
import (
"fmt"
"os"
"strconv"
"time"
"github.com/anthdm/superkit/event"
"github.com/anthdm/superkit/kit"
v "github.com/anthdm/superkit/validate"
"github.com/golang-jwt/jwt/v5"
)
var signupSchema = v.Schema{
"email": v.Rules(v.Email),
"password": v.Rules(
v.ContainsSpecial,
v.ContainsUpper,
v.Min(7),
v.Max(50),
),
"firstName": v.Rules(v.Min(2), v.Max(50)),
"lastName": v.Rules(v.Min(2), v.Max(50)),
}
func HandleSignupIndex(kit *kit.Kit) error {
return kit.Render(SignupIndex(SignupIndexPageData{}))
}
func HandleSignupCreate(kit *kit.Kit) error {
var values SignupFormValues
errors, ok := v.Request(kit.Request, &values, signupSchema)
if !ok {
return kit.Render(SignupForm(values, errors))
}
if values.Password != values.PasswordConfirm {
errors.Add("passwordConfirm", "passwords do not match")
return kit.Render(SignupForm(values, errors))
}
user, err := createUserFromFormValues(values)
if err != nil {
return err
}
token, err := createVerificationToken(user.ID)
if err != nil {
return err
}
event.Emit("auth.signup", UserWithVerificationToken{
Token: token,
User: user,
})
return kit.Render(ConfirmEmail(user.Email))
}
func createVerificationToken(userID int) (string, error) {
expiryStr := kit.Getenv("SUPERKIT_AUTH_EMAIL_VERIFICATION_EXPIRY_IN_HOURS", "1")
expiry, err := strconv.Atoi(expiryStr)
if err != nil {
expiry = 1
}
claims := jwt.RegisteredClaims{
Subject: fmt.Sprint(userID),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * time.Duration(expiry))),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(os.Getenv("SUPERKIT_SECRET")))
}

View file

@ -8,6 +8,13 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
// UserWithVerificationToken is a struct that will be sent over the
// auth.signup event. It holds the User struct and the Verification token string.
type UserWithVerificationToken struct {
User User
Token string
}
type Auth struct { type Auth struct {
UserID int UserID int
Email string Email string