superkit/bootstrap/plugins/auth/auth_handler.go
2024-06-16 10:28:09 +02:00

174 lines
4.3 KiB
Go

package auth
import (
"auth/app/db"
"cmp"
"database/sql"
"net/http"
"os"
"strconv"
"time"
"github.com/anthdm/gothkit/kit"
v "github.com/anthdm/gothkit/validate"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
const (
userSessionName = "user-session"
)
var authSchema = v.Schema{
"email": v.Rules(v.Email),
"password": v.Rules(v.Required),
}
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 HandleAuthIndex(kit *kit.Kit) error {
if kit.Auth().Check() {
redirectURL := cmp.Or(os.Getenv("SUPERKIT_AUTH_REDIRECT_AFTER_LOGIN"), "/profile")
return kit.Redirect(http.StatusSeeOther, redirectURL)
}
return kit.Render(AuthIndex(AuthIndexPageData{}))
}
func HandleAuthCreate(kit *kit.Kit) error {
var values LoginFormValues
errors, ok := v.Request(kit.Request, &values, authSchema)
if !ok {
return kit.Render(LoginForm(values, errors))
}
var user User
err := db.Query.NewSelect().
Model(&user).
Where("user.email = ?", values.Email).
Scan(kit.Request.Context())
if err != nil {
if err == sql.ErrNoRows {
errors.Add("credentials", "invalid credentials")
return kit.Render(LoginForm(values, errors))
}
}
err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(values.Password))
if err != nil {
errors.Add("credentials", "invalid credentials")
return kit.Render(LoginForm(values, errors))
}
// todo: use the kit.Getenv instead of the comp thingy
skipVerify := cmp.Or(os.Getenv("SUPERKIT_AUTH_SKIP_VERIFY"), "false")
if skipVerify != "true" {
if user.EmailVerifiedAt.Equal(time.Time{}) {
errors.Add("verified", "please verify your email")
return kit.Render(LoginForm(values, errors))
}
}
sessionExpiryStr := os.Getenv("SUPERKIT_AUTH_SESSION_EXPIRY_IN_HOURS")
sessionExpiry, err := strconv.Atoi(sessionExpiryStr)
if err != nil {
sessionExpiry = 48
}
session := Session{
UserID: user.ID,
Token: uuid.New().String(),
CreatedAt: time.Now(),
LastLoginAt: time.Now(),
ExpiresAt: time.Now().Add(time.Hour * time.Duration(sessionExpiry)),
}
_, err = db.Query.NewInsert().
Model(&session).
Exec(kit.Request.Context())
if err != nil {
return err
}
// TODO change this with kit.Getenv
sess := kit.GetSession(userSessionName)
sess.Values["sessionToken"] = session.Token
sess.Save(kit.Request, kit.Response)
redirectURL := cmp.Or(os.Getenv("SUPERKIT_AUTH_REDIRECT_AFTER_LOGIN"), "/profile")
return kit.Redirect(http.StatusSeeOther, redirectURL)
}
func HandleAuthDelete(kit *kit.Kit) error {
sess := kit.GetSession(userSessionName)
defer func() {
sess.Values = map[any]any{}
sess.Save(kit.Request, kit.Response)
}()
_, err := db.Query.NewDelete().
Model((*Session)(nil)).
Where("token = ?", sess.Values["sessionToken"]).
Exec(kit.Request.Context())
if err != nil {
return err
}
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
}
return kit.Render(ConfirmEmail(user.Email))
}
func AuthenticateUser(kit *kit.Kit) (kit.Auth, error) {
auth := Auth{}
sess := kit.GetSession(userSessionName)
token, ok := sess.Values["sessionToken"]
if !ok {
return auth, nil
}
var session Session
err := db.Query.NewSelect().
Model(&session).
Relation("User").
Where("session.token = ? AND session.expires_at > ?", token, time.Now()).
Scan(kit.Request.Context())
if err != nil {
return auth, nil
}
// TODO: do we really need to check if the user is verified
// even if we check that already in the login process.
// if session.User.EmailVerifiedAt.Equal(time.Time{}) {
// return Auth{}, nil
// }
return Auth{
LoggedIn: true,
UserID: session.User.ID,
Email: session.User.Email,
}, nil
}