adding validated services? patching forcad_local.py
This commit is contained in:
96
OmCTF-2025/services/block_game/backend/route/auth.go
Normal file
96
OmCTF-2025/services/block_game/backend/route/auth.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"omctf.ru/block-game-backend/auth/session"
|
||||
"omctf.ru/block-game-backend/codegen/ent"
|
||||
"omctf.ru/block-game-backend/codegen/ent/user"
|
||||
"omctf.ru/block-game-backend/db"
|
||||
"omctf.ru/block-game-backend/utils"
|
||||
)
|
||||
|
||||
func Login(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := utils.GetJSONBody[struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}](r)
|
||||
if err != nil || req == nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := db.Client.User.Query().
|
||||
Where(user.Username(req.Username)).
|
||||
Where(user.Password(req.Password)).
|
||||
Only(ctx)
|
||||
|
||||
if ent.IsNotFound(err) {
|
||||
http.Error(w, "Invalid credentials", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err != nil || user == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = session.SetUserId(w, r, user.ID)
|
||||
if session.IsInvalidSession(err) {
|
||||
http.Error(w, "Invalid session", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func Register(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := utils.GetJSONBody[struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}](r)
|
||||
if err != nil || req == nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := db.Client.User.CreateFrom(req).Save(ctx)
|
||||
if ent.IsValidationError(err) {
|
||||
http.Error(w, fmt.Sprintf("Invalid credentials: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if ent.IsConstraintError(err) {
|
||||
http.Error(w, "Name taken", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
if err != nil || user == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = session.SetUserId(w, r, user.ID)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't set the session after registering the user: %s\n", err)
|
||||
// anyway registering succeded
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
utils.RespondWithJSON(w, map[string]any{
|
||||
"id": user.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
err := session.ClearSession(w, r)
|
||||
if err != nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
321
OmCTF-2025/services/block_game/backend/route/levels.go
Normal file
321
OmCTF-2025/services/block_game/backend/route/levels.go
Normal file
@@ -0,0 +1,321 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"github.com/gorilla/mux"
|
||||
"omctf.ru/block-game-backend/auth"
|
||||
"omctf.ru/block-game-backend/codegen/ent"
|
||||
"omctf.ru/block-game-backend/codegen/ent/level"
|
||||
"omctf.ru/block-game-backend/db"
|
||||
"omctf.ru/block-game-backend/game"
|
||||
"omctf.ru/block-game-backend/messaging"
|
||||
"omctf.ru/block-game-backend/schema"
|
||||
"omctf.ru/block-game-backend/utils"
|
||||
)
|
||||
|
||||
func CreateLevel(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user, err := auth.GetUser(ctx)
|
||||
if err != nil || user == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := utils.GetJSONBody[struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Visibility string `json:"visibility"`
|
||||
Data schema.LevelData `json:"data"`
|
||||
Prize string `json:"prize"`
|
||||
}](r)
|
||||
if err != nil || req == nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Data.Size > 20 || req.Data.Size <= 0 {
|
||||
http.Error(w, "size must be between 1 and 20", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Data.Tiles) > 256 {
|
||||
http.Error(w, "max 256 tiles", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
level, err := db.Client.Level.CreateFrom(req).
|
||||
SetOwner(user).
|
||||
Save(ctx)
|
||||
|
||||
if ent.IsValidationError(err) {
|
||||
http.Error(w, fmt.Sprintf("Invalid request: %s", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if ent.IsConstraintError(err) {
|
||||
http.Error(w, "Name taken", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
if err != nil || level == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
utils.RespondWithJSON(w, map[string]any{
|
||||
"id": level.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func getPublicLevelMetadata(level *ent.Level) map[string]any {
|
||||
return map[string]any{
|
||||
"id": level.ID,
|
||||
"name": level.Name,
|
||||
"description": level.Description,
|
||||
"visibility": level.Visibility,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
PageSize = 20
|
||||
)
|
||||
|
||||
func ListLevels(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
currentUser, err := auth.GetUser(ctx)
|
||||
if err != nil || currentUser == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var resultLevels []*ent.Level
|
||||
|
||||
pageNumberStr := r.URL.Query().Get("page")
|
||||
if pageNumberStr == "" {
|
||||
// non-paged
|
||||
resultLevels, err = db.Client.Level.Query().Where(
|
||||
utils.LevelAccessibleBy(currentUser.ID),
|
||||
).Order(level.ByCreatedAt(sql.OrderDesc())).All(ctx)
|
||||
if err != nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
pageNumber, err := strconv.Atoi(pageNumberStr)
|
||||
if err != nil || pageNumber < 0 {
|
||||
http.Error(w, "invalid page number", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
resultLevels, err = db.Client.Level.Query().Where(
|
||||
utils.LevelAccessibleBy(currentUser.ID),
|
||||
).Order(level.ByCreatedAt(sql.OrderDesc())).Limit(PageSize).Offset(pageNumber * PageSize).All(ctx)
|
||||
if err != nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data := make([]any, 0, len(resultLevels))
|
||||
for _, lvl := range resultLevels {
|
||||
levelData := getPublicLevelMetadata(lvl)
|
||||
data = append(data, levelData)
|
||||
}
|
||||
|
||||
utils.RespondWithJSON(w, data)
|
||||
}
|
||||
|
||||
func GetLevel(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
currentUser, err := auth.GetUser(ctx)
|
||||
if err != nil || currentUser == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
levelId := mux.Vars(r)["levelId"]
|
||||
if levelId == "" {
|
||||
http.Error(w, "levelId missing", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
levelIdInt, err := strconv.Atoi(levelId)
|
||||
if err != nil {
|
||||
http.Error(w, "levelId invalid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
chosenLevel, err := db.Client.Level.Query().Where(
|
||||
level.ID(levelIdInt),
|
||||
utils.LevelAccessibleBy(currentUser.ID),
|
||||
).WithOwner().Only(ctx)
|
||||
if ent.IsNotFound(err) {
|
||||
http.Error(w, "level not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil || chosenLevel == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
levelOwner, err := chosenLevel.Edges.OwnerOrErr()
|
||||
if err != nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
privileged := levelOwner.ID == currentUser.ID
|
||||
|
||||
levelData := getPublicLevelMetadata(chosenLevel)
|
||||
levelData["data"] = chosenLevel.Data
|
||||
if privileged {
|
||||
levelData["prize"] = chosenLevel.Prize
|
||||
}
|
||||
utils.RespondWithJSON(w, levelData)
|
||||
}
|
||||
|
||||
func FindLevel(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
currentUser, err := auth.GetUser(ctx)
|
||||
if err != nil || currentUser == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
query := r.URL.Query().Get("name")
|
||||
if query == "" {
|
||||
http.Error(w, "query missing", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
foundLevel, err := db.Client.Level.Query().Where(
|
||||
level.Name(query),
|
||||
utils.LevelAccessibleBy(currentUser.ID),
|
||||
).Only(ctx)
|
||||
if ent.IsNotFound(err) {
|
||||
http.Error(w, "level not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil || foundLevel == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
levelData := getPublicLevelMetadata(foundLevel)
|
||||
utils.RespondWithJSON(w, levelData)
|
||||
}
|
||||
|
||||
func InviteToLevel(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
currentUser, err := auth.GetUser(ctx)
|
||||
if err != nil || currentUser == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := utils.GetJSONBody[struct {
|
||||
LevelId int `json:"level_id"`
|
||||
UserId int `json:"user_id"`
|
||||
}](r)
|
||||
if err != nil || req == nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
levelToInvite, err := db.Client.Level.Query().Where(
|
||||
level.ID(req.LevelId),
|
||||
utils.LevelAccessibleBy(currentUser.ID),
|
||||
).WithOwner().Only(ctx)
|
||||
if ent.IsNotFound(err) {
|
||||
http.Error(w, "level not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil || levelToInvite == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
levelOwner, err := levelToInvite.Edges.OwnerOrErr()
|
||||
if err != nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
if levelOwner.ID != currentUser.ID {
|
||||
http.Error(w, "only level owner can invite players", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
userToInvite, err := db.Client.User.Get(ctx, req.UserId)
|
||||
if ent.IsNotFound(err) {
|
||||
http.Error(w, "user not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil || userToInvite == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = levelToInvite.Update().
|
||||
AddInvitedPlayers(userToInvite).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func PlayLevel(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
currentUser, err := auth.GetUser(ctx)
|
||||
if err != nil || currentUser == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
levelId := mux.Vars(r)["levelId"]
|
||||
if levelId == "" {
|
||||
http.Error(w, "levelId missing", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
levelIdInt, err := strconv.Atoi(levelId)
|
||||
if err != nil {
|
||||
http.Error(w, "levelId invalid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
chosenLevel, err := db.Client.Level.Query().Where(
|
||||
level.ID(levelIdInt),
|
||||
utils.LevelAccessibleBy(currentUser.ID),
|
||||
).Only(ctx)
|
||||
|
||||
if ent.IsNotFound(err) {
|
||||
http.Error(w, "level not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil || chosenLevel == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := messaging.InitWebsocket(w, r)
|
||||
if err != nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
session := &game.Session{
|
||||
Conn: conn,
|
||||
UserId: currentUser.ID,
|
||||
LevelId: chosenLevel.ID,
|
||||
Tiles: game.NewTiles(chosenLevel.Data.Size, chosenLevel.Data.Tiles),
|
||||
}
|
||||
|
||||
session.Start()
|
||||
}
|
||||
70
OmCTF-2025/services/block_game/backend/route/user.go
Normal file
70
OmCTF-2025/services/block_game/backend/route/user.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"omctf.ru/block-game-backend/auth"
|
||||
"omctf.ru/block-game-backend/codegen/ent"
|
||||
"omctf.ru/block-game-backend/codegen/ent/predicate"
|
||||
"omctf.ru/block-game-backend/codegen/ent/user"
|
||||
"omctf.ru/block-game-backend/db"
|
||||
"omctf.ru/block-game-backend/utils"
|
||||
)
|
||||
|
||||
func Whoami(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := auth.GetUser(r.Context())
|
||||
if err != nil || user == nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondWithJSON(w, map[string]any{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
})
|
||||
}
|
||||
|
||||
func GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := auth.GetUser(r.Context())
|
||||
if err != nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := utils.GetJSONBody[struct {
|
||||
Id int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
}](r)
|
||||
if err != nil || req == nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Id == 0 && req.Username == "" {
|
||||
http.Error(w, "Either id or username must be provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
predicates := []predicate.User{}
|
||||
if req.Id != 0 {
|
||||
predicates = append(predicates, user.ID(req.Id))
|
||||
}
|
||||
if req.Username != "" {
|
||||
predicates = append(predicates, user.Username(req.Username))
|
||||
}
|
||||
|
||||
user, err := db.Client.User.Query().Where(predicates...).Only(r.Context())
|
||||
if ent.IsNotFound(err) {
|
||||
http.Error(w, "User not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
utils.BailInternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondWithJSON(w, map[string]any{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user