golang-template/internal/services/auth.go
Leo 43219268e9 feat: Add Sliding expiration for user_sessions
The user_session is valid for 24 hours, but the session will be bumped if it has been over an hour since the last update. An hour was set as a deadline, so that we would not update the user_session rows too often.
2026-08-17 23:47:11 +03:00

119 lines
2.3 KiB
Go

package services
import (
"context"
"crypto/subtle"
"database/sql"
"errors"
"strings"
"go-backend/internal/auth"
"go-backend/internal/database/sqlc"
)
type AuthService struct {
db *sql.DB
queries *sqlc.Queries
}
func NewAuthService(db *sql.DB, queries *sqlc.Queries) *AuthService {
return &AuthService{
db: db,
queries: queries,
}
}
type SessionWithToken struct {
Session sqlc.UserSession
Token string
}
func (s *AuthService) CreateSession(ctx context.Context, userID string) (*SessionWithToken, error) {
id, err := auth.GenerateSecureRandomString()
if err != nil {
return nil, err
}
secret, err := auth.GenerateSecureRandomString()
if err != nil {
return nil, err
}
secretHash := auth.HashSecret(secret)
token := id + "." + secret
session, err := s.queries.InsertSession(ctx, sqlc.InsertSessionParams{
ID: id,
SecretHash: secretHash,
UserID: userID,
})
if err != nil {
return nil, err
}
return &SessionWithToken{
Session: session,
Token: token,
}, nil
}
func (s *AuthService) ValidateSessionToken(ctx context.Context, token string) (*sqlc.UserSession, bool, error) {
tokenParts := strings.Split(token, ".")
if len(tokenParts) != 2 {
return nil, false, nil
}
sessionID := tokenParts[0]
sessionSecret := tokenParts[1]
session, err := s.queries.SelectSessionById(ctx, sessionID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, false, nil
}
return nil, false, err
}
if auth.CheckExpiration(session.LastVerifiedAt) {
err = s.queries.DeleteSessionById(ctx, sessionID)
if err != nil {
return nil, false, err
}
return nil, false, nil
}
tokenSecretHash := auth.HashSecret(sessionSecret)
if subtle.ConstantTimeCompare(tokenSecretHash, session.SecretHash) != 1 {
return nil, false, nil
}
if auth.CheckNeedForBump(session.LastVerifiedAt) {
session, err = s.queries.UpdateSessionVerifiedAt(ctx, sessionID)
if err != nil {
return nil, false, err
}
return &session, true, nil
}
return &session, false, nil
}
func (s *AuthService) DeleteSession(ctx context.Context, token string) error {
tokenParts := strings.Split(token, ".")
if len(tokenParts) != 2 {
return nil
}
sessionID := tokenParts[0]
err := s.queries.DeleteSessionById(ctx, sessionID)
return err
}