mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-18 14:27:17 +02:00
- sync the account's reports, bounties and standing from the HackerOne hacker API into bounty_reports, bounty_awards and bounty_accounts; money comes from /payments/earnings, totals stay per currency - /bounty-hub/<platform> as a Bounty Hub child in the sidebar, in the /scans idiom: stat strip, severity toggles, monthly chart with drag range, stage tabs with filtered counts, URL filters, expandable rows, keyboard navigation and a by-program view - program names open the Bounty Hub program sheet, which links back to the reports on that program and fetches unfetched scope on open - hourly report sync gated on bug bounty mode and the sync interval Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
261 lines
8.4 KiB
Python
261 lines
8.4 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import Column, Text, text
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.types import JSON
|
|
from sqlmodel import Field, SQLModel, UniqueConstraint
|
|
|
|
from shared.definitions.bounty_programs import ProgramSource
|
|
from shared.enums.target import TargetType
|
|
from shared.models.organization import OrganizationSummary
|
|
from shared.models.tag import TagSummary
|
|
from shared.utils.datetime import utc_now
|
|
|
|
|
|
class BountyProgram(SQLModel, table=True):
|
|
__tablename__ = "bounty_programs"
|
|
__table_args__ = (
|
|
UniqueConstraint("platform", "handle", name="uq_bounty_program_handle"),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
platform: str = Field(max_length=32, index=True)
|
|
source: str = Field(default=ProgramSource.API.value, max_length=16, index=True)
|
|
sources: list[str] = Field(
|
|
default_factory=list,
|
|
sa_column=Column(JSONB, nullable=False, server_default=text("'[]'::jsonb")),
|
|
)
|
|
handle: str = Field(max_length=200, index=True)
|
|
external_id: str | None = Field(default=None, max_length=100, index=True)
|
|
name: str = Field(max_length=300)
|
|
url: str | None = Field(default=None, max_length=500)
|
|
profile_picture: str | None = Field(
|
|
default=None, sa_column=Column(Text, nullable=True)
|
|
)
|
|
program_state: str = Field(max_length=16, index=True)
|
|
raw_state: str | None = Field(default=None, max_length=32)
|
|
joined: bool = Field(default=False, index=True)
|
|
joined_at: datetime | None = Field(default=None)
|
|
submission_state: str = Field(max_length=16, index=True)
|
|
offers_bounties: bool = Field(default=False, index=True)
|
|
open_scope: bool | None = Field(default=None)
|
|
gold_standard_safe_harbor: bool | None = Field(default=None)
|
|
currency: str | None = Field(default=None, max_length=16)
|
|
started_accepting_at: datetime | None = Field(default=None, index=True)
|
|
bookmarked: bool = Field(default=False, index=True)
|
|
reports_for_user: int | None = Field(default=None)
|
|
earnings_for_user: float | None = Field(default=None)
|
|
min_payout: float | None = Field(default=None)
|
|
max_payout: float | None = Field(default=None)
|
|
payout_currency: str | None = Field(default=None, max_length=16)
|
|
safe_harbor: str | None = Field(default=None, max_length=32)
|
|
requires_2fa: bool | None = Field(default=None)
|
|
scopes_synced_at: datetime | None = Field(default=None)
|
|
scope_access: str | None = Field(default=None, max_length=16)
|
|
synced_at: datetime = Field(default_factory=utc_now, index=True)
|
|
|
|
|
|
class BountyScope(SQLModel, table=True):
|
|
__tablename__ = "bounty_scopes"
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"program_id", "asset_type", "asset_identifier", name="uq_bounty_scope_asset"
|
|
),
|
|
)
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
program_id: uuid.UUID = Field(
|
|
foreign_key="bounty_programs.id", index=True, ondelete="CASCADE"
|
|
)
|
|
asset_type: str = Field(max_length=48, index=True)
|
|
asset_identifier: str = Field(max_length=1000)
|
|
scope_state: str = Field(max_length=16, index=True)
|
|
eligible_for_bounty: bool | None = Field(default=None)
|
|
max_severity: str | None = Field(default=None, max_length=16)
|
|
tier: str | None = Field(default=None, max_length=32)
|
|
instruction: str | None = Field(default=None, sa_column=Column(Text))
|
|
reference: str | None = Field(default=None, max_length=500)
|
|
target_value: str | None = Field(default=None, max_length=500, index=True)
|
|
target_type: TargetType | None = Field(default=None)
|
|
raw: dict | None = Field(default=None, sa_column=Column(JSON, nullable=True))
|
|
synced_at: datetime = Field(default_factory=utc_now)
|
|
|
|
|
|
class BountyEventRow(SQLModel, table=True):
|
|
__tablename__ = "bounty_events"
|
|
|
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
|
platform: str = Field(max_length=32, index=True)
|
|
program_id: uuid.UUID = Field(
|
|
foreign_key="bounty_programs.id", index=True, ondelete="CASCADE"
|
|
)
|
|
handle: str = Field(max_length=200, index=True)
|
|
program_name: str = Field(max_length=300)
|
|
kind: str = Field(max_length=32, index=True)
|
|
asset_type: str | None = Field(default=None, max_length=48)
|
|
asset_identifier: str | None = Field(default=None, max_length=1000)
|
|
detail: str | None = Field(default=None, max_length=500)
|
|
created_at: datetime = Field(default_factory=utc_now, index=True)
|
|
|
|
|
|
class BountyEventRead(BaseModel):
|
|
id: uuid.UUID
|
|
platform: str
|
|
handle: str
|
|
program_name: str
|
|
kind: str
|
|
label: str = ""
|
|
description: str = ""
|
|
icon: str = "circle-help"
|
|
tone: str = "muted"
|
|
actionable: bool = False
|
|
asset_type: str | None
|
|
asset_identifier: str | None
|
|
detail: str | None
|
|
created_at: datetime
|
|
|
|
|
|
class BountyScopeRead(BaseModel):
|
|
id: uuid.UUID
|
|
asset_type: str
|
|
asset_type_label: str
|
|
asset_group: str
|
|
icon: str
|
|
asset_identifier: str
|
|
scope_state: str
|
|
eligible_for_bounty: bool | None
|
|
max_severity: str | None
|
|
tier: str | None
|
|
instruction: str | None
|
|
target_value: str | None
|
|
target_type: str | None
|
|
importable: bool
|
|
already_target: bool = False
|
|
|
|
|
|
class BountyProgramRead(BaseModel):
|
|
id: uuid.UUID
|
|
platform: str
|
|
platform_label: str = ""
|
|
source: str = ProgramSource.API.value
|
|
source_label: str = ""
|
|
sources: list[str] = []
|
|
source_labels: list[str] = []
|
|
follow_label: str = ""
|
|
handle: str
|
|
name: str
|
|
url: str | None
|
|
profile_picture: str | None
|
|
program_state: str
|
|
raw_state: str | None
|
|
raw_state_label: str = ""
|
|
joined: bool
|
|
joined_at: datetime | None
|
|
submission_state: str
|
|
offers_bounties: bool
|
|
open_scope: bool | None
|
|
gold_standard_safe_harbor: bool | None
|
|
currency: str | None
|
|
started_accepting_at: datetime | None
|
|
bookmarked: bool
|
|
reports_for_user: int | None
|
|
earnings_for_user: float | None
|
|
min_payout: float | None = None
|
|
max_payout: float | None = None
|
|
payout_currency: str | None = None
|
|
safe_harbor: str | None = None
|
|
requires_2fa: bool | None = None
|
|
scopes_synced_at: datetime | None
|
|
scope_access: str | None = None
|
|
synced_at: datetime
|
|
in_scope_count: int = 0
|
|
out_of_scope_count: int = 0
|
|
importable_count: int = 0
|
|
imported_count: int = 0
|
|
watched: bool = False
|
|
watch_id: uuid.UUID | None = None
|
|
|
|
|
|
class BountyProgramDetail(BountyProgramRead):
|
|
scopes: list[BountyScopeRead] = []
|
|
unreachable: dict[str, int] = {}
|
|
|
|
|
|
class BountyImportRequest(BaseModel):
|
|
project_id: uuid.UUID
|
|
scope_ids: list[uuid.UUID] | None = None
|
|
include_out_of_scope: bool = False
|
|
group_by_program: bool = True
|
|
# blank falls back to the program name
|
|
organization_name: str | None = None
|
|
tags: list[str] = []
|
|
|
|
|
|
class BountyImportResult(BaseModel):
|
|
created: list[str]
|
|
existing: list[str]
|
|
skipped: list[str]
|
|
organization: OrganizationSummary | None = None
|
|
tags: list[TagSummary] = []
|
|
|
|
|
|
class PlatformCount(BaseModel):
|
|
platform: str
|
|
label: str
|
|
source: str
|
|
programs: int
|
|
private_programs: int = 0
|
|
feed_programs: int = 0
|
|
has_feed: bool = False
|
|
api_provider: str | None = None
|
|
supports_private: bool = False
|
|
credential: str = ""
|
|
note: str = ""
|
|
configured: bool = False
|
|
tracks_reports: bool = False
|
|
|
|
|
|
class BountyStatus(BaseModel):
|
|
configured: bool
|
|
platform: str
|
|
username: str | None
|
|
programs: int
|
|
private_programs: int
|
|
last_synced_at: datetime | None
|
|
sync_interval: str
|
|
next_sync_at: datetime | None = None
|
|
unseen_events: int = 0
|
|
platforms: list[PlatformCount] = []
|
|
source_counts: dict[str, int] = {}
|
|
feed_interval: str = ""
|
|
feed_synced_at: datetime | None = None
|
|
error: str | None = None
|
|
|
|
|
|
class BountySettingsRead(BaseModel):
|
|
platforms: list[PlatformCount] = []
|
|
sync_interval: str
|
|
feed_interval: str
|
|
feed_synced_at: datetime | None
|
|
feed_next_sync_at: datetime | None
|
|
feed_programs: int
|
|
feed_source: str
|
|
feed_url: str
|
|
feed_license: str
|
|
notify: bool
|
|
notify_events: list[str]
|
|
notifiable_events: list[str]
|
|
last_synced_at: datetime | None
|
|
next_sync_at: datetime | None
|
|
programs: int
|
|
events_recorded: int
|
|
|
|
|
|
class BountySettingsUpdate(BaseModel):
|
|
sync_interval: str | None = None
|
|
feed_interval: str | None = None
|
|
notify: bool | None = None
|
|
notify_events: list[str] | None = None
|