Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds paid hints #374

Merged
merged 4 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions _examples/simple/beast.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
[author]
name = "fristonio"
email = "[email protected]"
ssh_key = "ssh-rsa AAAAB3NzaC1y"
name = "hi"
email = "[email protected]"
ssh_key = "hi"

[challenge.metadata]
name = "simple"
flag = "BACKDOOR{SAMPLE_FLAG}"
type = "bare"
hints = ["simple_hint_1", "simple_hint_2"]
points = 100

[[challenge.metadata.hints]]
text = "simple_hint_1"
points = 10

[[challenge.metadata.hints]]
text = "simple_hint_2"
points = 20

[challenge.env]
apt_deps = ["gcc", "socat"]
setup_scripts = ["setup.sh"]
run_cmd = "socat tcp-l:10001,fork,reuseaddr exec:./pwn"
ports = [10001]
run_cmd = "socat tcp-l:10005,fork,reuseaddr exec:./pwn"
ports = [10005]
150 changes: 146 additions & 4 deletions api/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,116 @@ func usedPortsInfoHandler(c *gin.Context) {
})
}

func hintHandler(c *gin.Context) {
hintIDStr := c.Param("hintID")

if hintIDStr == "" {
c.JSON(http.StatusBadRequest, HTTPErrorResp{
Error: "Hint ID cannot be empty",
})
return
}

hintID, err := strconv.Atoi(hintIDStr)

if err != nil {
c.JSON(http.StatusBadRequest, HTTPPlainResp{
Message: "Hint Id format invalid",
})
return
}

username, err := coreUtils.GetUser(c.GetHeader("Authorization"))
if err != nil {
c.JSON(http.StatusUnauthorized, HTTPErrorResp{
Error: "Unauthorized user",
})
return
}

user, err := database.QueryFirstUserEntry("username", username)
if err != nil {
c.JSON(http.StatusUnauthorized, HTTPErrorResp{
Error: "Unauthorized user",
})
return
}

if user.Status == 1 {
c.JSON(http.StatusUnauthorized, HTTPErrorResp{
Error: "Banned user",
})
return
}

// Fetch hint details
hint, err := database.GetHintByID(uint(hintID))
if err != nil {
if err.Error() == "not_found" {
c.JSON(http.StatusNotFound, HTTPErrorResp{
Error: "Hint not found",
})
} else {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while processing the request",
})
}
return
}

// Check if the user has already taken the hint
hasTakenHint, err := database.UserHasTakenHint(user.ID, uint(hintID))
if err != nil {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while checking hint usage",
})
return
}

if c.Request.Method == "GET" {
if hasTakenHint {
c.JSON(http.StatusOK, HintResponse{
Description: hint.Description,
Points: hint.Points,
})
} else {
c.JSON(http.StatusOK, HintResponse{
Description: "Hint is not taken yet",
Points: hint.Points,
})
}
return
}

if hasTakenHint {
// If hint already taken, just return the description
c.JSON(http.StatusOK, HTTPPlainResp{
Message: hint.Description,

})
return
}

// Save user hint if not already taken
if err := database.SaveUserHint(user.ID, hint.ChallengeID, hint.HintID); err != nil {
if err.Error() == "Not enough points to take this hint" {
c.JSON(http.StatusForbidden, HTTPErrorResp{
Error: "You don't have enough points to take this hint",
})
return
}
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while saving the hint usage",
})
return
}

// Return the hint description after successfully taking it
c.JSON(http.StatusOK, HTTPPlainResp{
Message: hint.Description,
})
}

// Returns information about a challenge
// @Summary Returns all information about the challenges.
// @Description Returns all information about the challenges by the challenge name.
Expand Down Expand Up @@ -103,6 +213,22 @@ func challengeInfoHandler(c *gin.Context) {
challengeTags[index] = tags.TagName
}

hints, err := database.QueryHintsByChallengeID(challenge.ID)
if err != nil {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while fetching hints.",
})
return
}

hintInfos := make([]HintInfo, len(hints))
for i, hint := range hints {
hintInfos[i] = HintInfo{
ID: hint.HintID,
Points: hint.Points,
}
}

authHeader := c.GetHeader("Authorization")

values := strings.Split(authHeader, " ")
Expand All @@ -127,8 +253,8 @@ func challengeInfoHandler(c *gin.Context) {
Tags: challengeTags,
Status: challenge.Status,
Ports: challengePorts,
Hints: hintInfos,
FailSolveLimit: challenge.FailSolveLimit,
Hints: challenge.Hints,
Desc: challenge.Description,
Assets: strings.Split(challenge.Assets, core.DELIMITER),
AdditionalLinks: strings.Split(challenge.AdditionalLinks, core.DELIMITER),
Expand All @@ -148,8 +274,8 @@ func challengeInfoHandler(c *gin.Context) {
Tags: challengeTags,
Status: challenge.Status,
Ports: challengePorts,
Hints: hintInfos,
FailSolveLimit: challenge.FailSolveLimit,
Hints: challenge.Hints,
Desc: challenge.Description,
Assets: strings.Split(challenge.Assets, core.DELIMITER),
AdditionalLinks: strings.Split(challenge.AdditionalLinks, core.DELIMITER),
Expand Down Expand Up @@ -317,11 +443,28 @@ func challengesInfoHandler(c *gin.Context) {
challengeTags[index] = tags.TagName
}

// Get hints for this challenge
hints, err := database.QueryHintsByChallengeID(challenge.ID)
if err != nil {
log.Error(err)
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while processing the request.",
})
return
}

hintInfos := make([]HintInfo, len(hints))
for i, hint := range hints {
hintInfos[i] = HintInfo{
ID: hint.HintID,
Points: hint.Points,
}
// Get previous tries for the current user and challenge
previousTries, err := database.GetUserPreviousTries(user.ID, challenge.ID)
if err != nil {
log.Error(err)
previousTries = 0

}

availableChallenges[index] = ChallengeInfoResp{
Expand All @@ -332,8 +475,8 @@ func challengesInfoHandler(c *gin.Context) {
CreatedAt: challenge.CreatedAt,
Status: challenge.Status,
Ports: challengePorts,
Hints: hintInfos,
FailSolveLimit: challenge.FailSolveLimit,
Hints: challenge.Hints,
Desc: challenge.Description,
Points: challenge.Points,
Assets: strings.Split(challenge.Assets, core.DELIMITER),
Expand Down Expand Up @@ -665,7 +808,6 @@ func submissionsHandler(c *gin.Context) {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while fetching user details.",
})
return
}
if len(challenge) == 0 {
continue
Expand Down
1 change: 0 additions & 1 deletion api/manage.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,6 @@ func manageUploadHandler(c *gin.Context) {
Assets: config.Challenge.Metadata.Assets,
AdditionalLinks: config.Challenge.Metadata.AdditionalLinks,
Ports: config.Challenge.Env.Ports,
Hints: config.Challenge.Metadata.Hints,
PreReqs: config.Challenge.Metadata.PreReqs,
Desc: config.Challenge.Metadata.Description,
Points: config.Challenge.Metadata.Points,
Expand Down
13 changes: 11 additions & 2 deletions api/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ type UserSolveResp struct {
SolvedAt time.Time `json:"solvedAt"`
}

type HintInfo struct {
ID uint `json:"id"`
Points uint `json:"points"`
}

type HintResponse struct {
Description string `json:"description" example:"This is a hint"`
Points uint `json:"points" example:"10"`
}

type ChallengeInfoResp struct {
Name string `json:"name" example:"Web Challenge"`
ChallId uint `json:"id" example:"0"`
Expand All @@ -110,7 +120,7 @@ type ChallengeInfoResp struct {
FailSolveLimit int `json:"failSolveLimit" example:"5"`
PreReqs []string `json:"preReqs" example:"['web-php','simple']"`
Ports []uint32 `json:"ports" example:[3001, 3002]`
Hints string `json:"hints" example:"Try robots"`
Hints []HintInfo `json:"hints"`
Desc string `json:"description" example:"A simple web challenge"`
Points uint `json:"points" example:"50"`
SolvesNumber int `json:"solvesNumber" example:"100"`
Expand All @@ -128,7 +138,6 @@ type ChallengePreviewResp struct {
FailSolveLimit int `json:"failSolveLimit" example:"5"`
PreReqs []string `json:"preReqs" example:"['web-php','simple']"`
Ports []uint32 `json:"ports" example:[3001, 3002]`
Hints []string `json:"hints" example:"Try robots"`
Desc string `json:"description" example:"A simple web challenge"`
Points uint `json:"points" example:"50"`
}
Expand Down
2 changes: 2 additions & 0 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ func initGinRouter() *gin.Engine {
infoGroup.GET("/users", getAllUsersInfoHandler)
infoGroup.GET("/submissions", submissionsHandler)
infoGroup.GET("/tags", tagHandler)
infoGroup.GET("/hint/:hintID", hintHandler)
infoGroup.POST("/hint/:hintID",hintHandler)
}

// Notification route group
Expand Down
18 changes: 11 additions & 7 deletions core/config/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,17 @@ func (config *Challenge) ValidateRequiredFields(challdir string) error {
// sidecar = "" # Name of the sidecar if any used by the challenge.
// ```
type ChallengeMetadata struct {
Flag string `toml:"flag"`
Name string `toml:"name"`
Type string `toml:"type"`
Tags []string `toml:"tags"`
Sidecar string `toml:"sidecar"`
Description string `toml:"description"`
Hints []string `toml:"hints"`

Flag string `toml:"flag"`
Name string `toml:"name"`
Type string `toml:"type"`
Tags []string `toml:"tags"`
Sidecar string `toml:"sidecar"`
Description string `toml:"description"`
Hints []struct {
Text string `toml:"text"`
Points uint `toml:"points"`
} `toml:"hints"`
FailSolveLimit *int `toml:"failSolveLimit"`
PreReqs []string `toml:"preReqs"`
Points uint `toml:"points"`
Expand Down
1 change: 0 additions & 1 deletion core/database/challenges.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ type Challenge struct {
FailSolveLimit int `gorm:"default:-1"`
PreReqs string `gorm:"type:text"`
Sidecar string `gorm:"type:varchar(64)"`
Hints string `gorm:"type:text"`
Assets string `gorm:"type:text"`
AdditionalLinks string `gorm:"type:text"`
Description string `gorm:"type:text"`
Expand Down
6 changes: 5 additions & 1 deletion core/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ func init() {
log.Fatalf("Cannot create related models: %s", err)
}

Db.AutoMigrate(&Challenge{}, &Transaction{}, &Port{}, &User{}, &Tag{}, &Notification{})
if err := Db.SetupJoinTable(&User{}, "Hints", &UserHint{}); err != nil {
log.Fatalf("Cannot create related models: %s", err)
}

Db.AutoMigrate(&Challenge{}, &Transaction{}, &Port{}, &User{}, &Tag{}, &Notification{}, &Hint{})

users, err := QueryUserEntries("email", core.DEFAULT_USER_EMAIL)
if err != nil {
Expand Down
Loading