Build post chat and attachment workflows
This commit is contained in:
112
backend/internal/auth/sessions.go
Normal file
112
backend/internal/auth/sessions.go
Normal file
@@ -0,0 +1,112 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user