wip
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 1m8s

This commit is contained in:
2025-12-25 07:36:58 +01:00
parent 1c091dc924
commit aaddb84144
23 changed files with 379 additions and 366 deletions

View File

@@ -5,9 +5,9 @@ import (
"log/slog"
"net/http"
"net/url"
"spend-sparrow/internal/auth_types"
"spend-sparrow/internal/authentication/template"
"spend-sparrow/internal/core"
"spend-sparrow/internal/types"
"spend-sparrow/internal/utils"
"time"
)
@@ -79,7 +79,7 @@ func (handler HandlerImpl) handleSignIn() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
core.UpdateSpan(r)
user, err := utils.WaitMinimumTime(securityWaitDuration, func() (*User, error) {
user, err := utils.WaitMinimumTime(securityWaitDuration, func() (*auth_types.User, error) {
session := core.GetSession(r)
email := r.FormValue("email")
password := r.FormValue("password")
@@ -89,14 +89,14 @@ func (handler HandlerImpl) handleSignIn() http.HandlerFunc {
return nil, err
}
cookie := middleware.CreateSessionCookie(session.Id)
cookie := core.CreateSessionCookie(session.Id)
http.SetCookie(w, &cookie)
return user, nil
})
if err != nil {
if errors.Is(err, service.ErrInvalidCredentials) {
if errors.Is(err, ErrInvalidCredentials) {
utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Invalid email or password", http.StatusUnauthorized)
} else {
utils.TriggerToastWithStatus(r.Context(), w, r, "error", "An error occurred", http.StatusInternalServerError)
@@ -127,7 +127,7 @@ func (handler HandlerImpl) handleSignUpPage() http.HandlerFunc {
return
}
signUpComp := auth.SignInOrUpComp(false)
signUpComp := template.SignInOrUpComp(false)
handler.render.RenderLayout(r, w, signUpComp, nil)
}
}
@@ -147,7 +147,7 @@ func (handler HandlerImpl) handleSignUpVerifyPage() http.HandlerFunc {
return
}
signIn := auth.VerifyComp()
signIn := template.VerifyComp()
handler.render.RenderLayout(r, w, signIn, user)
}
}
@@ -180,7 +180,7 @@ func (handler HandlerImpl) handleSignUpVerifyResponsePage() http.HandlerFunc {
err := handler.service.VerifyUserEmail(r.Context(), token)
isVerified := err == nil
comp := auth.VerifyResponseComp(isVerified)
comp := template.VerifyResponseComp(isVerified)
var status int
if isVerified {
@@ -214,14 +214,14 @@ func (handler HandlerImpl) handleSignUp() http.HandlerFunc {
if err != nil {
switch {
case errors.Is(err, types.ErrInternal):
case errors.Is(err, core.ErrInternal):
utils.TriggerToastWithStatus(r.Context(), w, r, "error", "An error occurred", http.StatusInternalServerError)
return
case errors.Is(err, service.ErrInvalidEmail):
case errors.Is(err, ErrInvalidEmail):
utils.TriggerToastWithStatus(r.Context(), w, r, "error", "The email provided is invalid", http.StatusBadRequest)
return
case errors.Is(err, service.ErrInvalidPassword):
utils.TriggerToastWithStatus(r.Context(), w, r, "error", service.ErrInvalidPassword.Error(), http.StatusBadRequest)
case errors.Is(err, ErrInvalidPassword):
utils.TriggerToastWithStatus(r.Context(), w, r, "error", ErrInvalidPassword.Error(), http.StatusBadRequest)
return
}
// If err is "service.ErrAccountExists", then just continue
@@ -270,7 +270,7 @@ func (handler HandlerImpl) handleDeleteAccountPage() http.HandlerFunc {
return
}
comp := auth.DeleteAccountComp()
comp := template.DeleteAccountComp()
handler.render.RenderLayout(r, w, comp, user)
}
}
@@ -289,7 +289,7 @@ func (handler HandlerImpl) handleDeleteAccountComp() http.HandlerFunc {
err := handler.service.DeleteAccount(r.Context(), user, password)
if err != nil {
if errors.Is(err, service.ErrInvalidCredentials) {
if errors.Is(err, ErrInvalidCredentials) {
utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Password not correct", http.StatusBadRequest)
} else {
utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Internal Server Error", http.StatusInternalServerError)
@@ -314,7 +314,7 @@ func (handler HandlerImpl) handleChangePasswordPage() http.HandlerFunc {
return
}
comp := auth.ChangePasswordComp(isPasswordReset)
comp := template.ChangePasswordComp(isPasswordReset)
handler.render.RenderLayout(r, w, comp, user)
}
}
@@ -353,7 +353,7 @@ func (handler HandlerImpl) handleForgotPasswordPage() http.HandlerFunc {
return
}
comp := auth.ResetPasswordComp()
comp := template.ResetPasswordComp()
handler.render.RenderLayout(r, w, comp, user)
}
}

View File

@@ -8,7 +8,6 @@ import (
"net/mail"
"spend-sparrow/internal/auth_types"
"spend-sparrow/internal/core"
"spend-sparrow/internal/db"
mailTemplate "spend-sparrow/internal/template/mail"
"spend-sparrow/internal/types"
"strings"
@@ -53,13 +52,13 @@ type Service interface {
type ServiceImpl struct {
db Db
random core.Random
clock Clock
mail Mail
clock core.Clock
mail core.Mail
serverSettings *types.Settings
}
func NewService(db db.Auth, random Random, clock Clock, mail Mail, serverSettings *types.Settings) *HandlerImpl {
return &HandlerImpl{
func NewService(db Db, random core.Random, clock core.Clock, mail core.Mail, serverSettings *types.Settings) *ServiceImpl {
return &ServiceImpl{
db: db,
random: random,
clock: clock,
@@ -68,13 +67,13 @@ func NewService(db db.Auth, random Random, clock Clock, mail Mail, serverSetting
}
}
func (service HandlerImpl) SignIn(ctx context.Context, session *auth_types.Session, email string, password string) (*auth_type.Session, *auth_types.User, error) {
func (service ServiceImpl) SignIn(ctx context.Context, session *auth_types.Session, email string, password string) (*auth_types.Session, *auth_types.User, error) {
user, err := service.db.GetUserByEmail(ctx, email)
if err != nil {
if errors.Is(err, db.ErrNotFound) {
if errors.Is(err, core.ErrNotFound) {
return nil, nil, ErrInvalidCredentials
} else {
return nil, nil, types.ErrInternal
return nil, nil, core.ErrInternal
}
}
@@ -86,36 +85,36 @@ func (service HandlerImpl) SignIn(ctx context.Context, session *auth_types.Sessi
newSession, err := service.createSession(ctx, user.Id)
if err != nil {
return nil, nil, types.ErrInternal
return nil, nil, core.ErrInternal
}
err = service.db.DeleteSession(ctx, session.Id)
if err != nil {
return nil, nil, types.ErrInternal
return nil, nil, core.ErrInternal
}
tokens, err := service.db.GetTokensBySessionIdAndType(ctx, session.Id, types.TokenTypeCsrf)
tokens, err := service.db.GetTokensBySessionIdAndType(ctx, session.Id, auth_types.TokenTypeCsrf)
if err != nil {
return nil, nil, types.ErrInternal
return nil, nil, core.ErrInternal
}
for _, token := range tokens {
err = service.db.DeleteToken(ctx, token.Token)
if err != nil {
return nil, nil, types.ErrInternal
return nil, nil, core.ErrInternal
}
}
return newSession, user, nil
}
func (service HandlerImpl) SignInSession(ctx context.Context, sessionId string) (*auth_types.Session, *auth_types.User, error) {
func (service ServiceImpl) SignInSession(ctx context.Context, sessionId string) (*auth_types.Session, *auth_types.User, error) {
if sessionId == "" {
return nil, nil, ErrSessionIdInvalid
}
session, err := service.db.GetSession(ctx, sessionId)
if err != nil {
return nil, nil, types.ErrInternal
return nil, nil, core.ErrInternal
}
if session.ExpiresAt.Before(service.clock.Now()) {
_ = service.db.DeleteSession(ctx, sessionId)
@@ -128,16 +127,16 @@ func (service HandlerImpl) SignInSession(ctx context.Context, sessionId string)
user, err := service.db.GetUser(ctx, session.UserId)
if err != nil {
return nil, nil, types.ErrInternal
return nil, nil, core.ErrInternal
}
return session, user, nil
}
func (service HandlerImpl) SignInAnonymous(ctx context.Context) (*auth_types.Session, error) {
func (service ServiceImpl) SignInAnonymous(ctx context.Context) (*auth_types.Session, error) {
session, err := service.createSession(ctx, uuid.Nil)
if err != nil {
return nil, types.ErrInternal
return nil, core.ErrInternal
}
slog.InfoContext(ctx, "anonymous session created", "session-id", session.Id)
@@ -145,7 +144,7 @@ func (service HandlerImpl) SignInAnonymous(ctx context.Context) (*auth_types.Ses
return session, nil
}
func (service HandlerImpl) SignUp(ctx context.Context, email string, password string) (*auth_types.User, error) {
func (service ServiceImpl) SignUp(ctx context.Context, email string, password string) (*auth_types.User, error) {
_, err := mail.ParseAddress(email)
if err != nil {
return nil, ErrInvalidEmail
@@ -157,37 +156,37 @@ func (service HandlerImpl) SignUp(ctx context.Context, email string, password st
userId, err := service.random.UUID(ctx)
if err != nil {
return nil, types.ErrInternal
return nil, core.ErrInternal
}
salt, err := service.random.Bytes(ctx, 16)
if err != nil {
return nil, types.ErrInternal
return nil, core.ErrInternal
}
hash := GetHashPassword(password, salt)
user := types.NewUser(userId, email, false, nil, false, hash, salt, service.clock.Now())
user := auth_types.NewUser(userId, email, false, nil, false, hash, salt, service.clock.Now())
err = service.db.InsertUser(ctx, user)
if err != nil {
if errors.Is(err, db.ErrAlreadyExists) {
if errors.Is(err, core.ErrAlreadyExists) {
return nil, ErrAccountExists
} else {
return nil, types.ErrInternal
return nil, core.ErrInternal
}
}
return user, nil
}
func (service HandlerImpl) SendVerificationMail(ctx context.Context, userId uuid.UUID, email string) {
tokens, err := service.db.GetTokensByUserIdAndType(ctx, userId, types.TokenTypeEmailVerify)
if err != nil && !errors.Is(err, db.ErrNotFound) {
func (service ServiceImpl) SendVerificationMail(ctx context.Context, userId uuid.UUID, email string) {
tokens, err := service.db.GetTokensByUserIdAndType(ctx, userId, auth_types.TokenTypeEmailVerify)
if err != nil && !errors.Is(err, core.ErrNotFound) {
return
}
var token *types.Token
var token *auth_types.Token
if len(tokens) > 0 {
token = tokens[0]
@@ -199,11 +198,11 @@ func (service HandlerImpl) SendVerificationMail(ctx context.Context, userId uuid
return
}
token = types.NewToken(
token = auth_types.NewToken(
userId,
"",
newTokenStr,
types.TokenTypeEmailVerify,
auth_types.TokenTypeEmailVerify,
service.clock.Now(),
service.clock.Now().Add(24*time.Hour))
@@ -223,29 +222,29 @@ func (service HandlerImpl) SendVerificationMail(ctx context.Context, userId uuid
service.mail.SendMail(ctx, email, "Welcome to spend-sparrow", w.String())
}
func (service HandlerImpl) VerifyUserEmail(ctx context.Context, tokenStr string) error {
func (service ServiceImpl) VerifyUserEmail(ctx context.Context, tokenStr string) error {
if tokenStr == "" {
return types.ErrInternal
return core.ErrInternal
}
token, err := service.db.GetToken(ctx, tokenStr)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
user, err := service.db.GetUser(ctx, token.UserId)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
if token.Type != types.TokenTypeEmailVerify {
return types.ErrInternal
if token.Type != auth_types.TokenTypeEmailVerify {
return core.ErrInternal
}
now := service.clock.Now()
if token.ExpiresAt.Before(now) {
return types.ErrInternal
return core.ErrInternal
}
user.EmailVerified = true
@@ -253,21 +252,21 @@ func (service HandlerImpl) VerifyUserEmail(ctx context.Context, tokenStr string)
err = service.db.UpdateUser(ctx, user)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
_ = service.db.DeleteToken(ctx, token.Token)
return nil
}
func (service HandlerImpl) SignOut(ctx context.Context, sessionId string) error {
func (service ServiceImpl) SignOut(ctx context.Context, sessionId string) error {
return service.db.DeleteSession(ctx, sessionId)
}
func (service HandlerImpl) DeleteAccount(ctx context.Context, user *auth_types.User, currPass string) error {
func (service ServiceImpl) DeleteAccount(ctx context.Context, user *auth_types.User, currPass string) error {
userDb, err := service.db.GetUser(ctx, user.Id)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
currHash := GetHashPassword(currPass, userDb.Salt)
@@ -285,7 +284,7 @@ func (service HandlerImpl) DeleteAccount(ctx context.Context, user *auth_types.U
return nil
}
func (service HandlerImpl) ChangePassword(ctx context.Context, user *auth_types.User, sessionId string, currPass, newPass string) error {
func (service ServiceImpl) ChangePassword(ctx context.Context, user *auth_types.User, sessionId string, currPass, newPass string) error {
if !isPasswordValid(newPass) {
return ErrInvalidPassword
}
@@ -310,13 +309,13 @@ func (service HandlerImpl) ChangePassword(ctx context.Context, user *auth_types.
sessions, err := service.db.GetSessions(ctx, user.Id)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
for _, s := range sessions {
if s.Id != sessionId {
err = service.db.DeleteSession(ctx, s.Id)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
}
}
@@ -324,7 +323,7 @@ func (service HandlerImpl) ChangePassword(ctx context.Context, user *auth_types.
return nil
}
func (service HandlerImpl) SendForgotPasswordMail(ctx context.Context, email string) error {
func (service ServiceImpl) SendForgotPasswordMail(ctx context.Context, email string) error {
tokenStr, err := service.random.String(ctx, 32)
if err != nil {
return err
@@ -332,38 +331,38 @@ func (service HandlerImpl) SendForgotPasswordMail(ctx context.Context, email str
user, err := service.db.GetUserByEmail(ctx, email)
if err != nil {
if errors.Is(err, db.ErrNotFound) {
if errors.Is(err, core.ErrNotFound) {
return nil
} else {
return types.ErrInternal
return core.ErrInternal
}
}
token := types.NewToken(
token := auth_types.NewToken(
user.Id,
"",
tokenStr,
types.TokenTypePasswordReset,
auth_types.TokenTypePasswordReset,
service.clock.Now(),
service.clock.Now().Add(15*time.Minute))
err = service.db.InsertToken(ctx, token)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
var mail strings.Builder
err = mailTemplate.ResetPassword(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &mail)
if err != nil {
slog.ErrorContext(ctx, "Could not render reset password email", "err", err)
return types.ErrInternal
return core.ErrInternal
}
service.mail.SendMail(ctx, email, "Reset Password", mail.String())
return nil
}
func (service HandlerImpl) ForgotPassword(ctx context.Context, tokenStr string, newPass string) error {
func (service ServiceImpl) ForgotPassword(ctx context.Context, tokenStr string, newPass string) error {
if !isPasswordValid(newPass) {
return ErrInvalidPassword
}
@@ -378,7 +377,7 @@ func (service HandlerImpl) ForgotPassword(ctx context.Context, tokenStr string,
return err
}
if token.Type != types.TokenTypePasswordReset ||
if token.Type != auth_types.TokenTypePasswordReset ||
token.ExpiresAt.Before(service.clock.Now()) {
return ErrTokenInvalid
}
@@ -386,7 +385,7 @@ func (service HandlerImpl) ForgotPassword(ctx context.Context, tokenStr string,
user, err := service.db.GetUser(ctx, token.UserId)
if err != nil {
slog.ErrorContext(ctx, "Could not get user from token", "err", err)
return types.ErrInternal
return core.ErrInternal
}
passHash := GetHashPassword(newPass, user.Salt)
@@ -399,26 +398,26 @@ func (service HandlerImpl) ForgotPassword(ctx context.Context, tokenStr string,
sessions, err := service.db.GetSessions(ctx, user.Id)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
for _, session := range sessions {
err = service.db.DeleteSession(ctx, session.Id)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
}
return nil
}
func (service HandlerImpl) IsCsrfTokenValid(ctx context.Context, tokenStr string, sessionId string) bool {
func (service ServiceImpl) IsCsrfTokenValid(ctx context.Context, tokenStr string, sessionId string) bool {
token, err := service.db.GetToken(ctx, tokenStr)
if err != nil {
return false
}
if token.Type != types.TokenTypeCsrf ||
if token.Type != auth_types.TokenTypeCsrf ||
token.SessionId != sessionId ||
token.ExpiresAt.Before(service.clock.Now()) {
return false
@@ -427,12 +426,12 @@ func (service HandlerImpl) IsCsrfTokenValid(ctx context.Context, tokenStr string
return true
}
func (service HandlerImpl) GetCsrfToken(ctx context.Context, session *auth_types.Session) (string, error) {
func (service ServiceImpl) GetCsrfToken(ctx context.Context, session *auth_types.Session) (string, error) {
if session == nil {
return "", types.ErrInternal
return "", core.ErrInternal
}
tokens, _ := service.db.GetTokensBySessionIdAndType(ctx, session.Id, types.TokenTypeCsrf)
tokens, _ := service.db.GetTokensBySessionIdAndType(ctx, session.Id, auth_types.TokenTypeCsrf)
if len(tokens) > 0 {
return tokens[0].Token, nil
@@ -440,19 +439,19 @@ func (service HandlerImpl) GetCsrfToken(ctx context.Context, session *auth_types
tokenStr, err := service.random.String(ctx, 32)
if err != nil {
return "", types.ErrInternal
return "", core.ErrInternal
}
token := types.NewToken(
token := auth_types.NewToken(
session.UserId,
session.Id,
tokenStr,
types.TokenTypeCsrf,
auth_types.TokenTypeCsrf,
service.clock.Now(),
service.clock.Now().Add(8*time.Hour))
err = service.db.InsertToken(ctx, token)
if err != nil {
return "", types.ErrInternal
return "", core.ErrInternal
}
slog.InfoContext(ctx, "CSRF-Token created", "token", tokenStr)
@@ -460,34 +459,34 @@ func (service HandlerImpl) GetCsrfToken(ctx context.Context, session *auth_types
return tokenStr, nil
}
func (service HandlerImpl) CleanupSessionsAndTokens(ctx context.Context) error {
func (service ServiceImpl) CleanupSessionsAndTokens(ctx context.Context) error {
err := service.db.DeleteOldSessions(ctx)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
err = service.db.DeleteOldTokens(ctx)
if err != nil {
return types.ErrInternal
return core.ErrInternal
}
return nil
}
func (service HandlerImpl) createSession(ctx context.Context, userId uuid.UUID) (*auth_types.Session, error) {
func (service ServiceImpl) createSession(ctx context.Context, userId uuid.UUID) (*auth_types.Session, error) {
sessionId, err := service.random.String(ctx, 32)
if err != nil {
return nil, types.ErrInternal
return nil, core.ErrInternal
}
createAt := service.clock.Now()
expiresAt := createAt.Add(24 * time.Hour)
session := types.NewSession(sessionId, userId, createAt, expiresAt)
session := auth_types.NewSession(sessionId, userId, createAt, expiresAt)
err = service.db.InsertSession(ctx, session)
if err != nil {
return nil, types.ErrInternal
return nil, core.ErrInternal
}
return session, nil