Compare commits
2 Commits
prod
...
restructur
| Author | SHA1 | Date | |
|---|---|---|---|
|
1c091dc924
|
|||
|
1db5c48553
|
@@ -1,4 +1,4 @@
|
||||
package types
|
||||
package auth_types
|
||||
|
||||
import (
|
||||
"time"
|
||||
@@ -1,11 +1,12 @@
|
||||
package db
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"spend-sparrow/internal/types"
|
||||
"spend-sparrow/internal/auth_types"
|
||||
"spend-sparrow/internal/core"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,36 +14,36 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type Auth interface {
|
||||
InsertUser(ctx context.Context, user *types.User) error
|
||||
UpdateUser(ctx context.Context, user *types.User) error
|
||||
GetUserByEmail(ctx context.Context, email string) (*types.User, error)
|
||||
GetUser(ctx context.Context, userId uuid.UUID) (*types.User, error)
|
||||
type Db interface {
|
||||
InsertUser(ctx context.Context, user *auth_types.User) error
|
||||
UpdateUser(ctx context.Context, user *auth_types.User) error
|
||||
GetUserByEmail(ctx context.Context, email string) (*auth_types.User, error)
|
||||
GetUser(ctx context.Context, userId uuid.UUID) (*auth_types.User, error)
|
||||
DeleteUser(ctx context.Context, userId uuid.UUID) error
|
||||
|
||||
InsertToken(ctx context.Context, token *types.Token) error
|
||||
GetToken(ctx context.Context, token string) (*types.Token, error)
|
||||
GetTokensByUserIdAndType(ctx context.Context, userId uuid.UUID, tokenType types.TokenType) ([]*types.Token, error)
|
||||
GetTokensBySessionIdAndType(ctx context.Context, sessionId string, tokenType types.TokenType) ([]*types.Token, error)
|
||||
InsertToken(ctx context.Context, token *auth_types.Token) error
|
||||
GetToken(ctx context.Context, token string) (*auth_types.Token, error)
|
||||
GetTokensByUserIdAndType(ctx context.Context, userId uuid.UUID, tokenType auth_types.TokenType) ([]*auth_types.Token, error)
|
||||
GetTokensBySessionIdAndType(ctx context.Context, sessionId string, tokenType auth_types.TokenType) ([]*auth_types.Token, error)
|
||||
DeleteToken(ctx context.Context, token string) error
|
||||
|
||||
InsertSession(ctx context.Context, session *types.Session) error
|
||||
GetSession(ctx context.Context, sessionId string) (*types.Session, error)
|
||||
GetSessions(ctx context.Context, userId uuid.UUID) ([]*types.Session, error)
|
||||
InsertSession(ctx context.Context, session *auth_types.Session) error
|
||||
GetSession(ctx context.Context, sessionId string) (*auth_types.Session, error)
|
||||
GetSessions(ctx context.Context, userId uuid.UUID) ([]*auth_types.Session, error)
|
||||
DeleteSession(ctx context.Context, sessionId string) error
|
||||
DeleteOldSessions(ctx context.Context) error
|
||||
DeleteOldTokens(ctx context.Context) error
|
||||
}
|
||||
|
||||
type AuthSqlite struct {
|
||||
type DbSqlite struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewAuthSqlite(db *sqlx.DB) *AuthSqlite {
|
||||
return &AuthSqlite{db: db}
|
||||
func NewDbSqlite(db *sqlx.DB) *DbSqlite {
|
||||
return &DbSqlite{db: db}
|
||||
}
|
||||
|
||||
func (db AuthSqlite) InsertUser(ctx context.Context, user *types.User) error {
|
||||
func (db DbSqlite) InsertUser(ctx context.Context, user *auth_types.User) error {
|
||||
_, err := db.db.ExecContext(ctx, `
|
||||
INSERT INTO user (user_id, email, email_verified, email_verified_at, is_admin, password, salt, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
@@ -50,17 +51,17 @@ func (db AuthSqlite) InsertUser(ctx context.Context, user *types.User) error {
|
||||
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "email") {
|
||||
return ErrAlreadyExists
|
||||
return core.ErrAlreadyExists
|
||||
}
|
||||
|
||||
slog.ErrorContext(ctx, "SQL error InsertUser", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) UpdateUser(ctx context.Context, user *types.User) error {
|
||||
func (db DbSqlite) UpdateUser(ctx context.Context, user *auth_types.User) error {
|
||||
_, err := db.db.ExecContext(ctx, `
|
||||
UPDATE user
|
||||
SET email_verified = ?, email_verified_at = ?, password = ?
|
||||
@@ -69,13 +70,13 @@ func (db AuthSqlite) UpdateUser(ctx context.Context, user *types.User) error {
|
||||
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "SQL error UpdateUser", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) GetUserByEmail(ctx context.Context, email string) (*types.User, error) {
|
||||
func (db DbSqlite) GetUserByEmail(ctx context.Context, email string) (*auth_types.User, error) {
|
||||
var (
|
||||
userId uuid.UUID
|
||||
emailVerified bool
|
||||
@@ -92,17 +93,17 @@ func (db AuthSqlite) GetUserByEmail(ctx context.Context, email string) (*types.U
|
||||
WHERE email = ?`, email).Scan(&userId, &emailVerified, &emailVerifiedAt, &password, &salt, &createdAt)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
return nil, core.ErrNotFound
|
||||
} else {
|
||||
slog.ErrorContext(ctx, "SQL error GetUser", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
}
|
||||
|
||||
return types.NewUser(userId, email, emailVerified, emailVerifiedAt, isAdmin, password, salt, createdAt), nil
|
||||
return auth_types.NewUser(userId, email, emailVerified, emailVerifiedAt, isAdmin, password, salt, createdAt), nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) GetUser(ctx context.Context, userId uuid.UUID) (*types.User, error) {
|
||||
func (db DbSqlite) GetUser(ctx context.Context, userId uuid.UUID) (*auth_types.User, error) {
|
||||
var (
|
||||
email string
|
||||
emailVerified bool
|
||||
@@ -119,92 +120,92 @@ func (db AuthSqlite) GetUser(ctx context.Context, userId uuid.UUID) (*types.User
|
||||
WHERE user_id = ?`, userId).Scan(&email, &emailVerified, &emailVerifiedAt, &password, &salt, &createdAt)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
return nil, core.ErrNotFound
|
||||
} else {
|
||||
slog.ErrorContext(ctx, "SQL error GetUser", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
}
|
||||
|
||||
return types.NewUser(userId, email, emailVerified, emailVerifiedAt, isAdmin, password, salt, createdAt), nil
|
||||
return auth_types.NewUser(userId, email, emailVerified, emailVerifiedAt, isAdmin, password, salt, createdAt), nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) DeleteUser(ctx context.Context, userId uuid.UUID) error {
|
||||
func (db DbSqlite) DeleteUser(ctx context.Context, userId uuid.UUID) error {
|
||||
tx, err := db.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not start transaction", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM account WHERE user_id = ?", userId)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
slog.ErrorContext(ctx, "Could not delete accounts", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM token WHERE user_id = ?", userId)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
slog.ErrorContext(ctx, "Could not delete user tokens", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM session WHERE user_id = ?", userId)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
slog.ErrorContext(ctx, "Could not delete sessions", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM user WHERE user_id = ?", userId)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
slog.ErrorContext(ctx, "Could not delete user", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM treasure_chest WHERE user_id = ?", userId)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
slog.ErrorContext(ctx, "Could not delete user", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM \"transaction\" WHERE user_id = ?", userId)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
slog.ErrorContext(ctx, "Could not delete user", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not commit transaction", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) InsertToken(ctx context.Context, token *types.Token) error {
|
||||
func (db DbSqlite) InsertToken(ctx context.Context, token *auth_types.Token) error {
|
||||
_, err := db.db.ExecContext(ctx, `
|
||||
INSERT INTO token (user_id, session_id, type, token, created_at, expires_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`, token.UserId, token.SessionId, token.Type, token.Token, token.CreatedAt, token.ExpiresAt)
|
||||
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not insert token", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) GetToken(ctx context.Context, token string) (*types.Token, error) {
|
||||
func (db DbSqlite) GetToken(ctx context.Context, token string) (*auth_types.Token, error) {
|
||||
var (
|
||||
userId uuid.UUID
|
||||
sessionId string
|
||||
tokenType types.TokenType
|
||||
tokenType auth_types.TokenType
|
||||
createdAtStr string
|
||||
expiresAtStr string
|
||||
createdAt time.Time
|
||||
@@ -219,29 +220,29 @@ func (db AuthSqlite) GetToken(ctx context.Context, token string) (*types.Token,
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
slog.InfoContext(ctx, "Token not found", "token", token)
|
||||
return nil, ErrNotFound
|
||||
return nil, core.ErrNotFound
|
||||
} else {
|
||||
slog.ErrorContext(ctx, "Could not get token", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
}
|
||||
|
||||
createdAt, err = time.Parse(time.RFC3339, createdAtStr)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not parse token.created_at", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
|
||||
expiresAt, err = time.Parse(time.RFC3339, expiresAtStr)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not parse token.expires_at", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
|
||||
return types.NewToken(userId, sessionId, token, tokenType, createdAt, expiresAt), nil
|
||||
return auth_types.NewToken(userId, sessionId, token, tokenType, createdAt, expiresAt), nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) GetTokensByUserIdAndType(ctx context.Context, userId uuid.UUID, tokenType types.TokenType) ([]*types.Token, error) {
|
||||
func (db DbSqlite) GetTokensByUserIdAndType(ctx context.Context, userId uuid.UUID, tokenType auth_types.TokenType) ([]*auth_types.Token, error) {
|
||||
query, err := db.db.QueryContext(ctx, `
|
||||
SELECT token, created_at, expires_at
|
||||
FROM token
|
||||
@@ -250,13 +251,13 @@ func (db AuthSqlite) GetTokensByUserIdAndType(ctx context.Context, userId uuid.U
|
||||
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not get token", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
|
||||
return getTokensFromQuery(ctx, query, userId, "", tokenType)
|
||||
}
|
||||
|
||||
func (db AuthSqlite) GetTokensBySessionIdAndType(ctx context.Context, sessionId string, tokenType types.TokenType) ([]*types.Token, error) {
|
||||
func (db DbSqlite) GetTokensBySessionIdAndType(ctx context.Context, sessionId string, tokenType auth_types.TokenType) ([]*auth_types.Token, error) {
|
||||
query, err := db.db.QueryContext(ctx, `
|
||||
SELECT token, created_at, expires_at
|
||||
FROM token
|
||||
@@ -265,14 +266,14 @@ func (db AuthSqlite) GetTokensBySessionIdAndType(ctx context.Context, sessionId
|
||||
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not get token", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
|
||||
return getTokensFromQuery(ctx, query, uuid.Nil, sessionId, tokenType)
|
||||
}
|
||||
|
||||
func getTokensFromQuery(ctx context.Context, query *sql.Rows, userId uuid.UUID, sessionId string, tokenType types.TokenType) ([]*types.Token, error) {
|
||||
var tokens []*types.Token
|
||||
func getTokensFromQuery(ctx context.Context, query *sql.Rows, userId uuid.UUID, sessionId string, tokenType auth_types.TokenType) ([]*auth_types.Token, error) {
|
||||
var tokens []*auth_types.Token
|
||||
|
||||
hasRows := false
|
||||
for query.Next() {
|
||||
@@ -289,54 +290,54 @@ func getTokensFromQuery(ctx context.Context, query *sql.Rows, userId uuid.UUID,
|
||||
err := query.Scan(&token, &createdAtStr, &expiresAtStr)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not scan token", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
|
||||
createdAt, err = time.Parse(time.RFC3339, createdAtStr)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not parse token.created_at", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
|
||||
expiresAt, err = time.Parse(time.RFC3339, expiresAtStr)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not parse token.expires_at", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
|
||||
tokens = append(tokens, types.NewToken(userId, sessionId, token, tokenType, createdAt, expiresAt))
|
||||
tokens = append(tokens, auth_types.NewToken(userId, sessionId, token, tokenType, createdAt, expiresAt))
|
||||
}
|
||||
|
||||
if !hasRows {
|
||||
return nil, ErrNotFound
|
||||
return nil, core.ErrNotFound
|
||||
}
|
||||
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) DeleteToken(ctx context.Context, token string) error {
|
||||
func (db DbSqlite) DeleteToken(ctx context.Context, token string) error {
|
||||
_, err := db.db.ExecContext(ctx, "DELETE FROM token WHERE token = ?", token)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not delete token", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) InsertSession(ctx context.Context, session *types.Session) error {
|
||||
func (db DbSqlite) InsertSession(ctx context.Context, session *auth_types.Session) error {
|
||||
_, err := db.db.ExecContext(ctx, `
|
||||
INSERT INTO session (session_id, user_id, created_at, expires_at)
|
||||
VALUES (?, ?, ?, ?)`, session.Id, session.UserId, session.CreatedAt, session.ExpiresAt)
|
||||
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not insert new session", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) GetSession(ctx context.Context, sessionId string) (*types.Session, error) {
|
||||
func (db DbSqlite) GetSession(ctx context.Context, sessionId string) (*auth_types.Session, error) {
|
||||
var (
|
||||
userId uuid.UUID
|
||||
createdAt time.Time
|
||||
@@ -350,56 +351,56 @@ func (db AuthSqlite) GetSession(ctx context.Context, sessionId string) (*types.S
|
||||
|
||||
if err != nil {
|
||||
slog.WarnContext(ctx, "Session not found", "session-id", sessionId, "err", err)
|
||||
return nil, ErrNotFound
|
||||
return nil, core.ErrNotFound
|
||||
}
|
||||
|
||||
return types.NewSession(sessionId, userId, createdAt, expiresAt), nil
|
||||
return auth_types.NewSession(sessionId, userId, createdAt, expiresAt), nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) GetSessions(ctx context.Context, userId uuid.UUID) ([]*types.Session, error) {
|
||||
var sessions []*types.Session
|
||||
func (db DbSqlite) GetSessions(ctx context.Context, userId uuid.UUID) ([]*auth_types.Session, error) {
|
||||
var sessions []*auth_types.Session
|
||||
err := db.db.SelectContext(ctx, &sessions, `
|
||||
SELECT *
|
||||
FROM session
|
||||
WHERE user_id = ?`, userId)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not get sessions", "err", err)
|
||||
return nil, types.ErrInternal
|
||||
return nil, core.ErrInternal
|
||||
}
|
||||
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) DeleteSession(ctx context.Context, sessionId string) error {
|
||||
func (db DbSqlite) DeleteSession(ctx context.Context, sessionId string) error {
|
||||
if sessionId != "" {
|
||||
_, err := db.db.ExecContext(ctx, "DELETE FROM session WHERE session_id = ?", sessionId)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not delete session", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) DeleteOldSessions(ctx context.Context) error {
|
||||
func (db DbSqlite) DeleteOldSessions(ctx context.Context) error {
|
||||
_, err := db.db.ExecContext(ctx, `
|
||||
DELETE FROM session
|
||||
WHERE expires_at < datetime('now')`)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not delete old sessions", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db AuthSqlite) DeleteOldTokens(ctx context.Context) error {
|
||||
func (db DbSqlite) DeleteOldTokens(ctx context.Context) error {
|
||||
_, err := db.db.ExecContext(ctx, `
|
||||
DELETE FROM token
|
||||
WHERE expires_at < datetime('now')`)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not delete old tokens", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,36 +1,34 @@
|
||||
package handler
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"spend-sparrow/internal/authentication/template"
|
||||
"spend-sparrow/internal/core"
|
||||
"spend-sparrow/internal/handler/middleware"
|
||||
"spend-sparrow/internal/service"
|
||||
"spend-sparrow/internal/template/auth"
|
||||
"spend-sparrow/internal/types"
|
||||
"spend-sparrow/internal/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Auth interface {
|
||||
type Handler interface {
|
||||
Handle(router *http.ServeMux)
|
||||
}
|
||||
|
||||
type AuthImpl struct {
|
||||
service service.Auth
|
||||
type HandlerImpl struct {
|
||||
service Service
|
||||
render *core.Render
|
||||
}
|
||||
|
||||
func NewAuth(service service.Auth, render *core.Render) Auth {
|
||||
return AuthImpl{
|
||||
func NewHandler(service Service, render *core.Render) Handler {
|
||||
return HandlerImpl{
|
||||
service: service,
|
||||
render: render,
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) Handle(router *http.ServeMux) {
|
||||
func (handler HandlerImpl) Handle(router *http.ServeMux) {
|
||||
router.Handle("GET /auth/signin", handler.handleSignInPage())
|
||||
router.Handle("POST /api/auth/signin", handler.handleSignIn())
|
||||
|
||||
@@ -57,7 +55,7 @@ var (
|
||||
securityWaitDuration = 250 * time.Millisecond
|
||||
)
|
||||
|
||||
func (handler AuthImpl) handleSignInPage() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleSignInPage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -71,17 +69,17 @@ func (handler AuthImpl) handleSignInPage() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
comp := auth.SignInOrUpComp(true)
|
||||
comp := template.SignInOrUpComp(true)
|
||||
|
||||
handler.render.RenderLayout(r, w, comp, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleSignIn() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleSignIn() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
user, err := utils.WaitMinimumTime(securityWaitDuration, func() (*types.User, error) {
|
||||
user, err := utils.WaitMinimumTime(securityWaitDuration, func() (*User, error) {
|
||||
session := core.GetSession(r)
|
||||
email := r.FormValue("email")
|
||||
password := r.FormValue("password")
|
||||
@@ -114,7 +112,7 @@ func (handler AuthImpl) handleSignIn() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleSignUpPage() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleSignUpPage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -134,7 +132,7 @@ func (handler AuthImpl) handleSignUpPage() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleSignUpVerifyPage() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleSignUpVerifyPage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -154,7 +152,7 @@ func (handler AuthImpl) handleSignUpVerifyPage() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleVerifyResendComp() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleVerifyResendComp() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -173,7 +171,7 @@ func (handler AuthImpl) handleVerifyResendComp() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleSignUpVerifyResponsePage() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleSignUpVerifyResponsePage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -195,7 +193,7 @@ func (handler AuthImpl) handleSignUpVerifyResponsePage() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleSignUp() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleSignUp() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -233,7 +231,7 @@ func (handler AuthImpl) handleSignUp() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleSignOut() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleSignOut() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -262,7 +260,7 @@ func (handler AuthImpl) handleSignOut() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleDeleteAccountPage() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleDeleteAccountPage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -277,7 +275,7 @@ func (handler AuthImpl) handleDeleteAccountPage() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleDeleteAccountComp() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleDeleteAccountComp() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -303,7 +301,7 @@ func (handler AuthImpl) handleDeleteAccountComp() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleChangePasswordPage() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleChangePasswordPage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -321,7 +319,7 @@ func (handler AuthImpl) handleChangePasswordPage() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleChangePasswordComp() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleChangePasswordComp() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -345,7 +343,7 @@ func (handler AuthImpl) handleChangePasswordComp() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleForgotPasswordPage() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleForgotPasswordPage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -360,7 +358,7 @@ func (handler AuthImpl) handleForgotPasswordPage() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleForgotPasswordComp() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleForgotPasswordComp() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -383,7 +381,7 @@ func (handler AuthImpl) handleForgotPasswordComp() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler AuthImpl) handleForgotPasswordResponseComp() http.HandlerFunc {
|
||||
func (handler HandlerImpl) handleForgotPasswordResponseComp() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
core.UpdateSpan(r)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"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"
|
||||
@@ -25,39 +27,39 @@ var (
|
||||
ErrTokenInvalid = errors.New("token is invalid")
|
||||
)
|
||||
|
||||
type Auth interface {
|
||||
SignUp(ctx context.Context, email string, password string) (*types.User, error)
|
||||
type Service interface {
|
||||
SignUp(ctx context.Context, email string, password string) (*auth_types.User, error)
|
||||
SendVerificationMail(ctx context.Context, userId uuid.UUID, email string)
|
||||
VerifyUserEmail(ctx context.Context, token string) error
|
||||
|
||||
SignIn(ctx context.Context, session *types.Session, email string, password string) (*types.Session, *types.User, error)
|
||||
SignInSession(ctx context.Context, sessionId string) (*types.Session, *types.User, error)
|
||||
SignInAnonymous(ctx context.Context) (*types.Session, error)
|
||||
SignIn(ctx context.Context, session *auth_types.Session, email string, password string) (*auth_types.Session, *auth_types.User, error)
|
||||
SignInSession(ctx context.Context, sessionId string) (*auth_types.Session, *auth_types.User, error)
|
||||
SignInAnonymous(ctx context.Context) (*auth_types.Session, error)
|
||||
SignOut(ctx context.Context, sessionId string) error
|
||||
|
||||
DeleteAccount(ctx context.Context, user *types.User, currPass string) error
|
||||
DeleteAccount(ctx context.Context, user *auth_types.User, currPass string) error
|
||||
|
||||
ChangePassword(ctx context.Context, user *types.User, sessionId string, currPass, newPass string) error
|
||||
ChangePassword(ctx context.Context, user *auth_types.User, sessionId string, currPass, newPass string) error
|
||||
|
||||
SendForgotPasswordMail(ctx context.Context, email string) error
|
||||
ForgotPassword(ctx context.Context, token string, newPass string) error
|
||||
|
||||
IsCsrfTokenValid(ctx context.Context, tokenStr string, sessionId string) bool
|
||||
GetCsrfToken(ctx context.Context, session *types.Session) (string, error)
|
||||
GetCsrfToken(ctx context.Context, session *auth_types.Session) (string, error)
|
||||
|
||||
CleanupSessionsAndTokens(ctx context.Context) error
|
||||
}
|
||||
|
||||
type AuthImpl struct {
|
||||
db db.Auth
|
||||
random Random
|
||||
type ServiceImpl struct {
|
||||
db Db
|
||||
random core.Random
|
||||
clock Clock
|
||||
mail Mail
|
||||
serverSettings *types.Settings
|
||||
}
|
||||
|
||||
func NewAuth(db db.Auth, random Random, clock Clock, mail Mail, serverSettings *types.Settings) *AuthImpl {
|
||||
return &AuthImpl{
|
||||
func NewService(db db.Auth, random Random, clock Clock, mail Mail, serverSettings *types.Settings) *HandlerImpl {
|
||||
return &HandlerImpl{
|
||||
db: db,
|
||||
random: random,
|
||||
clock: clock,
|
||||
@@ -66,7 +68,7 @@ func NewAuth(db db.Auth, random Random, clock Clock, mail Mail, serverSettings *
|
||||
}
|
||||
}
|
||||
|
||||
func (service AuthImpl) SignIn(ctx context.Context, session *types.Session, email string, password string) (*types.Session, *types.User, error) {
|
||||
func (service HandlerImpl) SignIn(ctx context.Context, session *auth_types.Session, email string, password string) (*auth_type.Session, *auth_types.User, error) {
|
||||
user, err := service.db.GetUserByEmail(ctx, email)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNotFound) {
|
||||
@@ -106,7 +108,7 @@ func (service AuthImpl) SignIn(ctx context.Context, session *types.Session, emai
|
||||
return newSession, user, nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) SignInSession(ctx context.Context, sessionId string) (*types.Session, *types.User, error) {
|
||||
func (service HandlerImpl) SignInSession(ctx context.Context, sessionId string) (*auth_types.Session, *auth_types.User, error) {
|
||||
if sessionId == "" {
|
||||
return nil, nil, ErrSessionIdInvalid
|
||||
}
|
||||
@@ -132,7 +134,7 @@ func (service AuthImpl) SignInSession(ctx context.Context, sessionId string) (*t
|
||||
return session, user, nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) SignInAnonymous(ctx context.Context) (*types.Session, error) {
|
||||
func (service HandlerImpl) SignInAnonymous(ctx context.Context) (*auth_types.Session, error) {
|
||||
session, err := service.createSession(ctx, uuid.Nil)
|
||||
if err != nil {
|
||||
return nil, types.ErrInternal
|
||||
@@ -143,7 +145,7 @@ func (service AuthImpl) SignInAnonymous(ctx context.Context) (*types.Session, er
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) SignUp(ctx context.Context, email string, password string) (*types.User, error) {
|
||||
func (service HandlerImpl) SignUp(ctx context.Context, email string, password string) (*auth_types.User, error) {
|
||||
_, err := mail.ParseAddress(email)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidEmail
|
||||
@@ -179,7 +181,7 @@ func (service AuthImpl) SignUp(ctx context.Context, email string, password strin
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) SendVerificationMail(ctx context.Context, userId uuid.UUID, email string) {
|
||||
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) {
|
||||
return
|
||||
@@ -221,7 +223,7 @@ func (service AuthImpl) SendVerificationMail(ctx context.Context, userId uuid.UU
|
||||
service.mail.SendMail(ctx, email, "Welcome to spend-sparrow", w.String())
|
||||
}
|
||||
|
||||
func (service AuthImpl) VerifyUserEmail(ctx context.Context, tokenStr string) error {
|
||||
func (service HandlerImpl) VerifyUserEmail(ctx context.Context, tokenStr string) error {
|
||||
if tokenStr == "" {
|
||||
return types.ErrInternal
|
||||
}
|
||||
@@ -258,11 +260,11 @@ func (service AuthImpl) VerifyUserEmail(ctx context.Context, tokenStr string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) SignOut(ctx context.Context, sessionId string) error {
|
||||
func (service HandlerImpl) SignOut(ctx context.Context, sessionId string) error {
|
||||
return service.db.DeleteSession(ctx, sessionId)
|
||||
}
|
||||
|
||||
func (service AuthImpl) DeleteAccount(ctx context.Context, user *types.User, currPass string) error {
|
||||
func (service HandlerImpl) 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
|
||||
@@ -283,7 +285,7 @@ func (service AuthImpl) DeleteAccount(ctx context.Context, user *types.User, cur
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) ChangePassword(ctx context.Context, user *types.User, sessionId string, currPass, newPass string) error {
|
||||
func (service HandlerImpl) ChangePassword(ctx context.Context, user *auth_types.User, sessionId string, currPass, newPass string) error {
|
||||
if !isPasswordValid(newPass) {
|
||||
return ErrInvalidPassword
|
||||
}
|
||||
@@ -322,7 +324,7 @@ func (service AuthImpl) ChangePassword(ctx context.Context, user *types.User, se
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) SendForgotPasswordMail(ctx context.Context, email string) error {
|
||||
func (service HandlerImpl) SendForgotPasswordMail(ctx context.Context, email string) error {
|
||||
tokenStr, err := service.random.String(ctx, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -361,7 +363,7 @@ func (service AuthImpl) SendForgotPasswordMail(ctx context.Context, email string
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) ForgotPassword(ctx context.Context, tokenStr string, newPass string) error {
|
||||
func (service HandlerImpl) ForgotPassword(ctx context.Context, tokenStr string, newPass string) error {
|
||||
if !isPasswordValid(newPass) {
|
||||
return ErrInvalidPassword
|
||||
}
|
||||
@@ -410,7 +412,7 @@ func (service AuthImpl) ForgotPassword(ctx context.Context, tokenStr string, new
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) IsCsrfTokenValid(ctx context.Context, tokenStr string, sessionId string) bool {
|
||||
func (service HandlerImpl) IsCsrfTokenValid(ctx context.Context, tokenStr string, sessionId string) bool {
|
||||
token, err := service.db.GetToken(ctx, tokenStr)
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -425,7 +427,7 @@ func (service AuthImpl) IsCsrfTokenValid(ctx context.Context, tokenStr string, s
|
||||
return true
|
||||
}
|
||||
|
||||
func (service AuthImpl) GetCsrfToken(ctx context.Context, session *types.Session) (string, error) {
|
||||
func (service HandlerImpl) GetCsrfToken(ctx context.Context, session *auth_types.Session) (string, error) {
|
||||
if session == nil {
|
||||
return "", types.ErrInternal
|
||||
}
|
||||
@@ -458,7 +460,7 @@ func (service AuthImpl) GetCsrfToken(ctx context.Context, session *types.Session
|
||||
return tokenStr, nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) CleanupSessionsAndTokens(ctx context.Context) error {
|
||||
func (service HandlerImpl) CleanupSessionsAndTokens(ctx context.Context) error {
|
||||
err := service.db.DeleteOldSessions(ctx)
|
||||
if err != nil {
|
||||
return types.ErrInternal
|
||||
@@ -472,7 +474,7 @@ func (service AuthImpl) CleanupSessionsAndTokens(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service AuthImpl) createSession(ctx context.Context, userId uuid.UUID) (*types.Session, error) {
|
||||
func (service HandlerImpl) 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
|
||||
@@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package template
|
||||
|
||||
templ ChangePasswordComp(isPasswordReset bool) {
|
||||
<form
|
||||
1
internal/authentication/template/default.go
Normal file
1
internal/authentication/template/default.go
Normal file
@@ -0,0 +1 @@
|
||||
package template
|
||||
@@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package template
|
||||
|
||||
templ DeleteAccountComp() {
|
||||
<form
|
||||
@@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package template
|
||||
|
||||
templ ResetPasswordComp() {
|
||||
<form
|
||||
@@ -1,13 +1,13 @@
|
||||
package auth
|
||||
package template
|
||||
|
||||
templ SignInOrUpComp(isSignIn bool) {
|
||||
{{
|
||||
var postUrl string
|
||||
if isSignIn {
|
||||
postUrl = "/api/auth/signin"
|
||||
} else {
|
||||
postUrl = "/api/auth/signup"
|
||||
}
|
||||
var postUrl string
|
||||
if isSignIn {
|
||||
postUrl = "/api/auth/signin"
|
||||
} else {
|
||||
postUrl = "/api/auth/signup"
|
||||
}
|
||||
}}
|
||||
<form
|
||||
class="max-w-xl px-2 mx-auto flex flex-col gap-4 h-full justify-center"
|
||||
@@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package template
|
||||
|
||||
templ VerifyComp() {
|
||||
<main class="h-full">
|
||||
@@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package template
|
||||
|
||||
templ VerifyResponseComp(isVerified bool) {
|
||||
<main>
|
||||
@@ -2,7 +2,7 @@ package core
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"spend-sparrow/internal/types"
|
||||
"spend-sparrow/internal/auth_types"
|
||||
)
|
||||
|
||||
type ContextKey string
|
||||
@@ -10,13 +10,13 @@ type ContextKey string
|
||||
var SessionKey ContextKey = "session"
|
||||
var UserKey ContextKey = "user"
|
||||
|
||||
func GetUser(r *http.Request) *types.User {
|
||||
func GetUser(r *http.Request) *auth_types.User {
|
||||
obj := r.Context().Value(UserKey)
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
user, ok := obj.(*types.User)
|
||||
user, ok := obj.(*auth_types.User)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@@ -24,16 +24,28 @@ func GetUser(r *http.Request) *types.User {
|
||||
return user
|
||||
}
|
||||
|
||||
func GetSession(r *http.Request) *types.Session {
|
||||
func GetSession(r *http.Request) *auth_types.Session {
|
||||
obj := r.Context().Value(SessionKey)
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
session, ok := obj.(*types.Session)
|
||||
session, ok := obj.(*auth_types.Session)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
func CreateSessionCookie(sessionId string) http.Cookie {
|
||||
return http.Cookie{
|
||||
Name: "id",
|
||||
Value: sessionId,
|
||||
MaxAge: 60 * 60 * 8, // 8 hours
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
Path: "/",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package core
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"spend-sparrow/internal/db"
|
||||
"spend-sparrow/internal/service"
|
||||
"spend-sparrow/internal/utils"
|
||||
"strings"
|
||||
|
||||
@@ -14,13 +12,13 @@ import (
|
||||
|
||||
func HandleError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
switch {
|
||||
case errors.Is(err, service.ErrUnauthorized):
|
||||
case errors.Is(err, ErrUnauthorized):
|
||||
utils.TriggerToastWithStatus(r.Context(), w, r, "error", "You are not autorized to perform this operation.", http.StatusUnauthorized)
|
||||
return
|
||||
case errors.Is(err, service.ErrBadRequest):
|
||||
case errors.Is(err, ErrBadRequest):
|
||||
utils.TriggerToastWithStatus(r.Context(), w, r, "error", extractErrorMessage(err), http.StatusBadRequest)
|
||||
return
|
||||
case errors.Is(err, db.ErrNotFound):
|
||||
case errors.Is(err, ErrNotFound):
|
||||
utils.TriggerToastWithStatus(r.Context(), w, r, "error", extractErrorMessage(err), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
13
internal/core/error.go
Normal file
13
internal/core/error.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package core
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("the value does not exist")
|
||||
ErrAlreadyExists = errors.New("row already exists")
|
||||
|
||||
ErrInternal = errors.New("internal server error")
|
||||
ErrUnauthorized = errors.New("you are not authorized to perform this action")
|
||||
|
||||
ErrBadRequest = errors.New("bad request")
|
||||
)
|
||||
@@ -1,11 +1,10 @@
|
||||
package service
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"log/slog"
|
||||
"spend-sparrow/internal/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -28,7 +27,7 @@ func (r *RandomImpl) Bytes(ctx context.Context, tsize int) ([]byte, error) {
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Error generating random bytes", "err", err)
|
||||
return []byte{}, types.ErrInternal
|
||||
return []byte{}, ErrInternal
|
||||
}
|
||||
|
||||
return b, nil
|
||||
@@ -38,7 +37,7 @@ func (r *RandomImpl) String(ctx context.Context, size int) (string, error) {
|
||||
bytes, err := r.Bytes(ctx, size)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Error generating random string", "err", err)
|
||||
return "", types.ErrInternal
|
||||
return "", ErrInternal
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(bytes), nil
|
||||
@@ -48,7 +47,7 @@ func (r *RandomImpl) UUID(ctx context.Context) (uuid.UUID, error) {
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Error generating random UUID", "err", err)
|
||||
return uuid.Nil, types.ErrInternal
|
||||
return uuid.Nil, ErrInternal
|
||||
}
|
||||
|
||||
return id, nil
|
||||
@@ -1,10 +1,11 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/a-h/templ"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"spend-sparrow/internal/types"
|
||||
"spend-sparrow/internal/auth_types"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
||||
type Render struct {
|
||||
@@ -28,18 +29,18 @@ func (render *Render) Render(r *http.Request, w http.ResponseWriter, comp templ.
|
||||
render.RenderWithStatus(r, w, comp, http.StatusOK)
|
||||
}
|
||||
|
||||
func (render *Render) RenderLayout(r *http.Request, w http.ResponseWriter, slot templ.Component, user *types.User) {
|
||||
func (render *Render) RenderLayout(r *http.Request, w http.ResponseWriter, slot templ.Component, user *auth_types.User) {
|
||||
render.RenderLayoutWithStatus(r, w, slot, user, http.StatusOK)
|
||||
}
|
||||
|
||||
func (render *Render) RenderLayoutWithStatus(r *http.Request, w http.ResponseWriter, slot templ.Component, user *types.User, status int) {
|
||||
func (render *Render) RenderLayoutWithStatus(r *http.Request, w http.ResponseWriter, slot templ.Component, user *auth_types.User, status int) {
|
||||
userComp := render.getUserComp(user)
|
||||
layout := Layout(slot, userComp, user != nil, r.URL.Path)
|
||||
|
||||
render.RenderWithStatus(r, w, layout, status)
|
||||
}
|
||||
|
||||
func (render *Render) getUserComp(user *types.User) templ.Component {
|
||||
func (render *Render) getUserComp(user *auth_types.User) templ.Component {
|
||||
if user != nil {
|
||||
return UserComp(user.Email)
|
||||
} else {
|
||||
|
||||
@@ -5,33 +5,28 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"spend-sparrow/internal/types"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("the value does not exist")
|
||||
ErrAlreadyExists = errors.New("row already exists")
|
||||
"spend-sparrow/internal/core"
|
||||
)
|
||||
|
||||
func TransformAndLogDbError(ctx context.Context, module string, r sql.Result, err error) error {
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrNotFound
|
||||
return core.ErrNotFound
|
||||
}
|
||||
slog.ErrorContext(ctx, "database sql", "module", module, "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
rows, err := r.RowsAffected()
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "database rows affected", "module", module, "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
if rows == 0 {
|
||||
slog.InfoContext(ctx, "row not found", "module", module)
|
||||
return ErrNotFound
|
||||
return core.ErrNotFound
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"spend-sparrow/internal/types"
|
||||
"spend-sparrow/internal/core"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
@@ -25,7 +25,7 @@ func RunMigrations(ctx context.Context, db *sqlx.DB, pathPrefix string) error {
|
||||
driver, err := sqlite3.WithInstance(db.DB, &sqlite3.Config{})
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not create Migration instance", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
@@ -34,14 +34,14 @@ func RunMigrations(ctx context.Context, db *sqlx.DB, pathPrefix string) error {
|
||||
driver)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Could not create migrations instance", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
m.Log = migrationLogger{}
|
||||
|
||||
if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
slog.ErrorContext(ctx, "Could not run migrations", "err", err)
|
||||
return types.ErrInternal
|
||||
return core.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,15 +1 @@
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
func CreateSessionCookie(sessionId string) http.Cookie {
|
||||
return http.Cookie{
|
||||
Name: "id",
|
||||
Value: sessionId,
|
||||
MaxAge: 60 * 60 * 8, // 8 hours
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
Path: "/",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package service
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrBadRequest = errors.New("bad request")
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
)
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"spend-sparrow/internal/authentication"
|
||||
"spend-sparrow/internal/db"
|
||||
"spend-sparrow/internal/types"
|
||||
"strconv"
|
||||
@@ -17,13 +18,13 @@ import (
|
||||
const page_size = 25
|
||||
|
||||
type Transaction interface {
|
||||
Add(ctx context.Context, tx *sqlx.Tx, user *types.User, transaction types.Transaction) (*types.Transaction, error)
|
||||
Update(ctx context.Context, user *types.User, transaction types.Transaction) (*types.Transaction, error)
|
||||
Get(ctx context.Context, user *types.User, id string) (*types.Transaction, error)
|
||||
GetAll(ctx context.Context, user *types.User, filter types.TransactionItemsFilter) ([]*types.Transaction, error)
|
||||
Delete(ctx context.Context, user *types.User, id string) error
|
||||
Add(ctx context.Context, tx *sqlx.Tx, user *authentication.User, transaction types.Transaction) (*types.Transaction, error)
|
||||
Update(ctx context.Context, user *authentication.User, transaction types.Transaction) (*types.Transaction, error)
|
||||
Get(ctx context.Context, user *authentication.User, id string) (*types.Transaction, error)
|
||||
GetAll(ctx context.Context, user *authentication.User, filter types.TransactionItemsFilter) ([]*types.Transaction, error)
|
||||
Delete(ctx context.Context, user *authentication.User, id string) error
|
||||
|
||||
RecalculateBalances(ctx context.Context, user *types.User) error
|
||||
RecalculateBalances(ctx context.Context, user *authentication.User) error
|
||||
}
|
||||
|
||||
type TransactionImpl struct {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package auth
|
||||
@@ -1,10 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInternal = errors.New("internal server error")
|
||||
ErrUnauthorized = errors.New("you are not authorized to perform this action")
|
||||
)
|
||||
Reference in New Issue
Block a user