package usecase import ( "context" "errors" "git.sch9.ru/new_gate/ms-auth/config" "git.sch9.ru/new_gate/ms-auth/internal/models" "git.sch9.ru/new_gate/ms-auth/internal/users" "git.sch9.ru/new_gate/ms-auth/pkg" ) type UseCase struct { userRepo users.PgRepository sessionRepo users.ValkeyRepository cfg config.Config } func NewUseCase( userRepo users.PgRepository, sessionRepo users.ValkeyRepository, cfg config.Config, ) *UseCase { return &UseCase{ userRepo: userRepo, sessionRepo: sessionRepo, cfg: cfg, } } const ( TokenKey = "token" ) func (u *UseCase) CreateUser(ctx context.Context, username string, password string, role models.Role) (int32, error) { const op = "UseCase.CreateUser" token, ok := ctx.Value(TokenKey).(*models.JWT) if !ok { return 0, pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no token in context") } if !token.Role.HasPermission(models.Create, models.ResourceAnotherUser) { return 0, pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } id, err := u.userRepo.C().CreateUser(ctx, username, password, role) if err != nil { return 0, pkg.Wrap(nil, err, op, "can't create user") } return id, nil } func (u *UseCase) ReadUserById(ctx context.Context, id int32) (*models.User, error) { const op = "UseCase.ReadUserById" token, ok := ctx.Value(TokenKey).(*models.JWT) if !ok { return nil, pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no token in context") } if token.UserId == id && !token.Role.HasPermission(models.Read, models.ResourceMeUser) { return nil, pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } if token.UserId != id && !token.Role.HasPermission(models.Read, models.ResourceAnotherUser) { return nil, pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } user, err := u.userRepo.C().ReadUserById(ctx, id) if err != nil { return nil, pkg.Wrap(nil, err, op, "can't read user by id") } return user, nil } // ReadUserByUsername is for login only. There are no permission checks! DO NOT USE IT AS AN ENDPOINT RESPONSE! func (u *UseCase) ReadUserByUsername(ctx context.Context, username string) (*models.User, error) { const op = "UseCase.ReadUserByUsername" user, err := u.userRepo.C().ReadUserByUsername(ctx, username) if err != nil { return nil, pkg.Wrap(nil, err, op, "can't read user by username") } return user, nil } func (u *UseCase) UpdateUser(ctx context.Context, id int32, username *string, role *models.Role) error { const op = "UseCase.UpdateUser" token, ok := ctx.Value(TokenKey).(*models.JWT) if !ok { return pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no token in context") } if token.UserId == id && !token.Role.HasPermission(models.Update, models.ResourceMeUser) { return pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } if token.UserId != id && !token.Role.HasPermission(models.Update, models.ResourceAnotherUser) { return pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } tx, err := u.userRepo.BeginTx(ctx) if err != nil { return pkg.Wrap(nil, err, op, "cannot start transaction") } err = tx.UpdateUser(ctx, id, username, role) if err != nil { return pkg.Wrap(nil, errors.Join(err, tx.Rollback()), op, "cannot update user") } err = u.sessionRepo.DeleteAllSessions(ctx, id) if err != nil { return pkg.Wrap(nil, errors.Join(err, tx.Rollback()), op, "cannot delete all sessions") } err = tx.Commit() if err != nil { return pkg.Wrap(nil, err, op, "cannot commit transaction") } return nil } func (u *UseCase) DeleteUser(ctx context.Context, id int32) error { const op = "UseCase.DeleteUser" token, ok := ctx.Value(TokenKey).(*models.JWT) if !ok { return pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no token in context") } if token.UserId == id && !token.Role.HasPermission(models.Delete, models.ResourceMeUser) { return pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } if token.UserId != id && !token.Role.HasPermission(models.Delete, models.ResourceAnotherUser) { return pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } tx, err := u.userRepo.BeginTx(ctx) if err != nil { return pkg.Wrap(nil, err, op, "cannot start transaction") } err = tx.DeleteUser(ctx, id) if err != nil { return pkg.Wrap(nil, errors.Join(err, tx.Rollback()), op, "cannot delete user") } err = u.sessionRepo.DeleteAllSessions(ctx, id) if err != nil { return pkg.Wrap(nil, errors.Join(err, tx.Rollback()), op, "cannot delete all sessions") } err = tx.Commit() if err != nil { return pkg.Wrap(nil, err, op, "cannot commit transaction") } return nil } // CreateSession is for login only. There are no permission checks! DO NOT USE IT AS AN ENDPOINT RESPONSE! func (u *UseCase) CreateSession(ctx context.Context, userId int32, role models.Role, userAgent, ip string) (*models.Session, error) { const op = "UseCase.CreateSession" session, err := u.sessionRepo.CreateSession(ctx, userId, role, userAgent, ip) if err != nil { return nil, pkg.Wrap(nil, err, op, "cannot create session") } return session, nil } // ReadSession is for internal use only. There are no permission checks! DO NOT USE IT AS AN ENDPOINT RESPONSE! func (u *UseCase) ReadSession(ctx context.Context, sessionId string) (*models.Session, error) { const op = "UseCase.ReadSession" session, err := u.sessionRepo.ReadSession(ctx, sessionId) if err != nil { return nil, pkg.Wrap(nil, err, op, "cannot read session") } return session, nil } func (u *UseCase) UpdateSession(ctx context.Context, sessionId string) error { const op = "UseCase.UpdateSession" token, ok := ctx.Value(TokenKey).(*models.JWT) if !ok { return pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no token in context") } if token.SessionId != sessionId { return pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } if token.SessionId == sessionId && !token.Role.HasPermission(models.Update, models.ResourceOwnSession) { return pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } err := u.sessionRepo.UpdateSession(ctx, sessionId) if err != nil { return pkg.Wrap(nil, err, op, "cannot update session") } return nil } func (u *UseCase) DeleteSession(ctx context.Context, sessionId string) error { const op = "UseCase.DeleteSession" token, ok := ctx.Value(TokenKey).(*models.JWT) if !ok { return pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no token in context") } if token.SessionId != sessionId { return pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } if token.SessionId == sessionId && !token.Role.HasPermission(models.Delete, models.ResourceOwnSession) { return pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } err := u.sessionRepo.DeleteSession(ctx, sessionId) if err != nil { return pkg.Wrap(nil, err, op, "cannot delete session") } return nil } func (u *UseCase) DeleteAllSessions(ctx context.Context, userId int32) error { const op = "UseCase.DeleteAllSessions" token, ok := ctx.Value(TokenKey).(*models.JWT) if !ok { return pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no token in context") } if token.UserId != userId { return pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } if token.UserId == userId && !token.Role.HasPermission(models.Delete, models.ResourceOwnSession) { return pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } err := u.sessionRepo.DeleteAllSessions(ctx, userId) if err != nil { return pkg.Wrap(nil, err, op, "cannot delete all sessions") } return nil } func (u *UseCase) ListUsers(ctx context.Context, page int32, pageSize int32) ([]*models.User, int32, error) { const op = "UseCase.ListUsers" token, ok := ctx.Value(TokenKey).(*models.JWT) if !ok { return nil, 0, pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no token in context") } if !token.Role.HasPermission(models.Read, models.ResourceAnotherUser) { return nil, 0, pkg.Wrap(pkg.NoPermission, nil, op, "no permission") } usersList, count, err := u.userRepo.C().ListUsers(ctx, page, pageSize) if err != nil { return nil, 0, pkg.Wrap(nil, err, op, "can't list users") } return usersList, count, nil }