add ban by login attempts and fix sql

This commit is contained in:
Sergey Chubaryan 2025-02-22 12:37:36 +03:00
parent a27c8669fc
commit 00b8636b5a
7 changed files with 55 additions and 33 deletions

View File

@ -123,6 +123,7 @@ func (a *App) Run(p RunParams) {
eventRepo = repos.NewEventRepo(kafka)
userCache = cache.NewCacheInmemSharded[models.UserDTO](cache.ShardingTypeInteger)
loginAttemptsCache = cache.NewCacheInmem[string, int]()
jwtCache = cache.NewCacheInmemSharded[string](cache.ShardingTypeJWT)
linksCache = cache.NewCacheInmem[string, string]()
)
@ -140,6 +141,7 @@ func (a *App) Run(p RunParams) {
userCache.CheckExpired()
jwtCache.CheckExpired()
linksCache.CheckExpired()
loginAttemptsCache.CheckExpired()
}
}
}()
@ -150,6 +152,7 @@ func (a *App) Run(p RunParams) {
Password: passwordUtil,
UserRepo: userRepo,
UserCache: userCache,
LoginAttemptsCache: loginAttemptsCache,
JwtCache: jwtCache,
EventRepo: *eventRepo,
ActionTokenRepo: actionTokenRepo,

View File

@ -26,7 +26,7 @@ type actionTokenRepo struct {
func (a *actionTokenRepo) CreateActionToken(ctx context.Context, dto models.ActionTokenDTO) (*models.ActionTokenDTO, error) {
query := `
insert into
action_tokens (user_id, value, target, expiration)
action_tokens (user_id, value, target, expires_at)
values ($1, $2, $3, $4)
returning id;`
row := a.db.QueryRowContext(ctx, query, dto.UserId, dto.Value, dto.Target, dto.Expiration)
@ -51,7 +51,7 @@ func (a *actionTokenRepo) GetActionToken(ctx context.Context, value string, targ
select id, user_id from action_tokens
where
value=$1 and target=$2
and CURRENT_TIMESTAMP < expiration;`
and CURRENT_TIMESTAMP < expires_at;`
row := a.db.QueryRowContext(ctx, query, value, target)
err := row.Scan(&dto.Id, &dto.UserId)

View File

@ -93,7 +93,7 @@ func (u *userRepo) GetUserById(ctx context.Context, id string) (*models.UserDTO,
query := `
select id, email, secret, full_name, email_verified
from users where id = $1 and activated;`
from users where id = $1 and active;`
row := u.db.QueryRowContext(ctx, query, id)
dto := &models.UserDTO{}
@ -113,7 +113,7 @@ func (u *userRepo) GetUserByEmail(ctx context.Context, login string) (*models.Us
defer span.End()
query := `select id, email, secret, full_name, email_verified
from users where email = $1 and activated;`
from users where email = $1 and active;`
row := u.db.QueryRowContext(ctx, query, login)
dto := &models.UserDTO{}

View File

@ -54,6 +54,7 @@ type UserServiceDeps struct {
UserRepo repos.UserRepo
UserCache cache.Cache[string, models.UserDTO]
JwtCache cache.Cache[string, string]
LoginAttemptsCache cache.Cache[string, int]
EventRepo repos.EventRepo
ActionTokenRepo repos.ActionTokenRepo
Logger logger.Logger
@ -108,6 +109,22 @@ func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (
}
func (u *userService) AuthenticateUser(ctx context.Context, email, password string) (string, error) {
attempts, ok := u.deps.LoginAttemptsCache.Get(email)
if ok && attempts >= 4 {
return "", fmt.Errorf("too many bad login attempts")
}
token, err := u.authenticateUser(ctx, email, password)
if err != nil {
u.deps.LoginAttemptsCache.Set(email, attempts+1, cache.Expiration{Ttl: 30 * time.Second})
return "", err
}
u.deps.LoginAttemptsCache.Del(email)
return token, nil
}
func (u *userService) authenticateUser(ctx context.Context, email, password string) (string, error) {
user, err := u.deps.UserRepo.GetUserByEmail(ctx, email)
if err != nil {
return "", err

View File

@ -4,11 +4,13 @@ create table if not exists users (
secret varchar(256) not null,
full_name varchar(256) not null,
email_verified boolean not null default false,
active boolean,
active boolean default true,
created_at timestamp,
updated_at timestamp
);
alter table users alter column active set default true;
create index if not exists idx_users_email on users(email);
create or replace trigger trg_user_created

View File

@ -4,14 +4,15 @@ create table if not exists shortlinks (
expires_at timestamp not null,
created_at timestamp,
updated_at timestamp
);
create or replace trigger trg_shortlink_created
before insert on shortlinks
for each row
when new is distinct from old
execute function trg_proc_row_created();
create or replace trigger trg_shortlink_updated
before update on shortlinks
for each row when new is distinct from old
for each row
when (new is distinct from old)
execute function trg_proc_row_updated();

View File

@ -1,26 +1,25 @@
create table if not exists action_tokens (
id int primary key generated always as identity,
id int generated always as identity,
user_id int references users(id),
value text not null,
target text not null,
expires_at timestamp not null,
created_at timestamp,
updated_at timestamp
updated_at timestamp,
constraint pk_action_tokens_id primary key(id),
constraint check chk_action_tokens_target target in ('verify', 'restore')
constraint chk_action_tokens_target check(target in ('verify', 'restore'))
);
create index if not exists idx_action_tokens_value on actions_tokens(value);
create index if not exists idx_action_tokens_value on action_tokens(value);
create or replace trigger trg_action_token_created
before insert on action_tokens
for each row
when new is distinct from old
execute function trg_proc_row_created();
create or replace trigger trg_action_token_updated
before update on action_tokens
for each row
when new is distinct from old
when (new is distinct from old)
execute function trg_proc_row_updated();