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
106 lines
3.5 KiB
Go
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
|
|
}
|