Files
mira/backend/internal/auth/sessions.go
Cauê Faleiros 0b920da187
All checks were successful
CI / backend (push) Successful in 13m8s
CI / frontend (push) Successful in 10m46s
CI / docker (push) Successful in 2m59s
Build post chat and attachment workflows
2026-06-08 16:44:33 -03:00

113 lines
2.5 KiB
Go

package auth
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"time"
"mira/backend/internal/users"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
var ErrRefreshTokenNotFound = errors.New("refresh token not found")
type RefreshSession struct {
ID string
TokenHash string
User users.User
ExpiresAt time.Time
RevokedAt *time.Time
}
type SessionStore struct {
db *pgxpool.Pool
}
func NewSessionStore(db *pgxpool.Pool) SessionStore {
return SessionStore{db: db}
}
func GenerateRefreshToken() (string, error) {
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(bytes), nil
}
func HashRefreshToken(token string) string {
hash := sha256.Sum256([]byte(token))
return hex.EncodeToString(hash[:])
}
func (s SessionStore) CreateRefreshSession(ctx context.Context, userID string, tokenHash string, expiresAt time.Time) error {
_, err := s.db.Exec(ctx, `
INSERT INTO refresh_tokens (user_id, token_hash, expires_at)
VALUES ($1::uuid, $2, $3)
`, userID, tokenHash, expiresAt)
return err
}
func (s SessionStore) FindValidRefreshSession(ctx context.Context, tokenHash string) (RefreshSession, error) {
row := s.db.QueryRow(ctx, `
SELECT
refresh_tokens.id::text,
refresh_tokens.token_hash,
users.id::text,
users.email,
users.name,
users.password_hash,
users.role,
users.status,
users.created_at,
users.updated_at,
refresh_tokens.expires_at,
refresh_tokens.revoked_at
FROM refresh_tokens
INNER JOIN users ON users.id = refresh_tokens.user_id
WHERE refresh_tokens.token_hash = $1
AND refresh_tokens.revoked_at IS NULL
AND refresh_tokens.expires_at > now()
AND users.status = 'active'
`, tokenHash)
var session RefreshSession
err := row.Scan(
&session.ID,
&session.TokenHash,
&session.User.ID,
&session.User.Email,
&session.User.Name,
&session.User.PasswordHash,
&session.User.Role,
&session.User.Status,
&session.User.CreatedAt,
&session.User.UpdatedAt,
&session.ExpiresAt,
&session.RevokedAt,
)
if errors.Is(err, pgx.ErrNoRows) {
return RefreshSession{}, ErrRefreshTokenNotFound
}
if err != nil {
return RefreshSession{}, err
}
return session, nil
}
func (s SessionStore) RevokeRefreshSession(ctx context.Context, tokenHash string) error {
_, err := s.db.Exec(ctx, `
UPDATE refresh_tokens
SET revoked_at = now()
WHERE token_hash = $1
AND revoked_at IS NULL
`, tokenHash)
return err
}