Files
NexaVPN/backend/internal/auth/repository.go
nessi 830491cb0d chore: initial project scaffold with admin web, backend, desktop client, and deployment setup
Add monorepo structure for NexaVPN WireGuard control plane including:
- .gitignore for node_modules, build artifacts, and environment files
- README with project overview, monorepo layout, and quick start guide
- Admin web UI with React, Vite, TypeScript, and nginx reverse proxy
- API client with type definitions for users, devices, policies, gateways, and audit logs
- Admin pages for dashboard, users, devices, policies, g
2026-03-15 16:32:34 +01:00

106 lines
3.5 KiB
Go

package auth
import (
"context"
"errors"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
)
type PGRepository struct {
db *pgxpool.Pool
}
func NewPGRepository(db *pgxpool.Pool) *PGRepository {
return &PGRepository{db: db}
}
func (r *PGRepository) FindUserByUsername(ctx context.Context, username string) (UserRecord, error) {
const query = `
select u.id, u.username, u.display_name, r.name, u.password_hash, u.is_active
from users u
join roles r on r.id = u.role_id
where u.username = $1 and u.deleted_at is null
`
row := r.db.QueryRow(ctx, query, username)
record := UserRecord{}
if err := row.Scan(&record.ID, &record.Username, &record.DisplayName, &record.Role, &record.PasswordHash, &record.IsActive); err != nil {
return UserRecord{}, err
}
return record, nil
}
func (r *PGRepository) CreateSession(ctx context.Context, userID uuid.UUID, expiresAt time.Time, ipAddress string, userAgent string) (uuid.UUID, error) {
const query = `
insert into sessions (id, user_id, ip_address, user_agent, expires_at)
values ($1, $2, nullif($3, '')::inet, $4, $5)
`
id := uuid.New()
_, err := r.db.Exec(ctx, query, id, userID, ipAddress, userAgent, expiresAt)
return id, err
}
func (r *PGRepository) StoreRefreshToken(ctx context.Context, sessionID uuid.UUID, userID uuid.UUID, tokenHash string, expiresAt time.Time) error {
const query = `
insert into refresh_tokens (id, session_id, user_id, token_hash, expires_at)
values ($1, $2, $3, $4, $5)
`
_, err := r.db.Exec(ctx, query, uuid.New(), sessionID, userID, tokenHash, expiresAt)
return err
}
func (r *PGRepository) FindRefreshToken(ctx context.Context, tokenHash string) (UserRecord, uuid.UUID, error) {
const query = `
select u.id, u.username, u.display_name, roles.name, u.password_hash, u.is_active, rt.session_id
from refresh_tokens rt
join users u on u.id = rt.user_id
join roles on roles.id = u.role_id
where rt.token_hash = $1 and rt.revoked_at is null and rt.expires_at > now()
`
record := UserRecord{}
var sessionID uuid.UUID
row := r.db.QueryRow(ctx, query, tokenHash)
if err := row.Scan(&record.ID, &record.Username, &record.DisplayName, &record.Role, &record.PasswordHash, &record.IsActive, &sessionID); err != nil {
return UserRecord{}, uuid.Nil, err
}
if !record.IsActive {
return UserRecord{}, uuid.Nil, errors.New("user inactive")
}
return record, sessionID, nil
}
func (r *PGRepository) RevokeRefreshToken(ctx context.Context, tokenHash string) error {
const query = `update refresh_tokens set revoked_at = now() where token_hash = $1 and revoked_at is null`
_, err := r.db.Exec(ctx, query, tokenHash)
return err
}
func (r *PGRepository) HasUsers(ctx context.Context) (bool, error) {
var count int
if err := r.db.QueryRow(ctx, `select count(*) from users where deleted_at is null`).Scan(&count); err != nil {
return false, err
}
return count > 0, nil
}
func (r *PGRepository) CreateBootstrapAdmin(ctx context.Context, username, displayName, passwordHash string) (UserRecord, error) {
const query = `
insert into users (id, role_id, username, display_name, password_hash, is_active)
values ($1, (select id from roles where name = 'admin'), $2, $3, $4, true)
returning id, username, display_name, password_hash, is_active
`
record := UserRecord{Role: "admin"}
err := r.db.QueryRow(ctx, query, uuid.New(), username, displayName, passwordHash).
Scan(&record.ID, &record.Username, &record.DisplayName, &record.PasswordHash, &record.IsActive)
return record, err
}