├── .DS_Store ├── .gitignore ├── Caddyfile ├── LICENSE ├── Makefile ├── README.md ├── authentication ├── auth.go ├── login.go └── register.go ├── backup └── .gitignore ├── bin └── .gitignore ├── ctfsrc ├── announcement.go ├── challengeupload.go ├── flagvalidation.go ├── getchallenge.go ├── imexport.go ├── points.go ├── startstop.go └── teammanagement.go ├── database └── db.go ├── entities ├── announcements.go ├── backup.go ├── challenge.go ├── flag.go ├── points.go ├── sessions.go ├── startstop.go └── users.go ├── frontend ├── .gitignore ├── dist_collected │ ├── CTF.html │ ├── admin.html │ ├── assets │ │ ├── index-357cb75c.css │ │ ├── index-860e7d9b.js │ │ ├── index-bbff9fea.js │ │ └── map.geojson │ └── index.html ├── index.html └── src │ ├── App.svelte │ ├── app.css │ ├── lib │ ├── Admin.svelte │ └── Index.svelte │ └── main.js ├── frontend_dev ├── .gitignore ├── .vscode │ ├── extensions.json │ └── launch.json ├── README.md ├── astro.config.mjs ├── package.json ├── pnpm-lock.yaml ├── public │ ├── favicon.svg │ └── map.geojson ├── src │ ├── components │ │ ├── admin.svelte │ │ ├── ctfstats.svelte │ │ ├── globe.jsx │ │ └── login.svelte │ ├── env.d.ts │ └── pages │ │ ├── ctf.astro │ │ ├── dashboard.astro │ │ └── index.astro ├── svelte.config.js ├── tailwind.config.cjs └── tsconfig.json ├── go.mod ├── go.sum ├── main.go ├── servehtml ├── admin.go ├── ctf.go └── login.go └── startup.sh /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FADAMIS/ctf-portal/198ac2fdda1c8e6a33ad3272ca3703acb8c42bb4/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CTFCONTENTS/* 2 | !CTFCONTENTS/.gitignore 3 | 4 | bin/* 5 | !bin/.gitignore 6 | 7 | backup/* 8 | !backup/.gitignore -------------------------------------------------------------------------------- /Caddyfile: -------------------------------------------------------------------------------- 1 | :80 { 2 | reverse_proxy /api/* localhost:8888 3 | reverse_proxy /* localhost:3000 4 | } 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | - go build -o bin/ 3 | - sudo setcap 'cap_net_bind_service=+ep' bin/ctf-portal -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTF Portal 2 | 3 | Backend and frontend of CTF contest portal, originally made for HackDays but is generally usable. 4 | 5 | CTF challenges are stored in CTFCONTENTS directory - every challenge has it's own subdirectory 6 | 7 | ## TODO: 8 | - Finish adding challenges (frontend) 9 | - Add Announcments, count down and leaderboard to gameboard (frontend) 10 | 11 | --- 12 | 13 | **Made with Go and Svelte** 14 | 15 | *Nearly finished, in development* 16 | 17 | Developers: 18 | - Backend - [Fabucik](https://github.com/Fabucik) 19 | - Frontend - [DuckyScr](https://github.com/DuckyScr) 20 | -------------------------------------------------------------------------------- /authentication/auth.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/Fabucik/ctf-portal/database" 7 | "github.com/Fabucik/ctf-portal/entities" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func IsValidSession(session entities.Session) bool { 12 | /*dbJson, _ := os.ReadFile("./database/session-cookies.json") 13 | var sessions entities.Sessions 14 | json.Unmarshal(dbJson, &sessions) 15 | 16 | for i := 0; i < len(sessions.Sessions); i++ { 17 | if session == sessions.Sessions[i] { 18 | // if session exists BUT is expired, delete the session 19 | if isExpired(session.ExpiresIn) { 20 | sessions.Sessions = append(sessions.Sessions[:i], sessions.Sessions[i+1:]...) 21 | writableJson, _ := json.MarshalIndent(sessions, "", "\t") 22 | os.WriteFile("./database/session-cookies.json", writableJson, 0600) 23 | return false 24 | } 25 | 26 | return true 27 | } 28 | } 29 | 30 | return false*/ 31 | 32 | sessions := database.ReadSessions(database.GetOpenedDB()) 33 | for i := 0; i < len(sessions.Sessions); i++ { 34 | if session == sessions.Sessions[i] { 35 | return !isExpired(session.ExpiresIn) 36 | } 37 | } 38 | 39 | return false 40 | } 41 | 42 | func IsAdmin(ctx *gin.Context, session entities.Session) bool { 43 | if !IsValidSession(session) || IsValidSession(session) && session.Username != "admin" { 44 | ctx.JSON(http.StatusUnauthorized, gin.H{ 45 | "message": "Unauthorized", 46 | }) 47 | 48 | return false 49 | } 50 | 51 | return true 52 | } 53 | -------------------------------------------------------------------------------- /authentication/login.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/Fabucik/ctf-portal/database" 9 | "github.com/Fabucik/ctf-portal/entities" 10 | "github.com/gin-gonic/gin" 11 | "github.com/google/uuid" 12 | ) 13 | 14 | func Login(ctx *gin.Context) { 15 | var credentials entities.User 16 | ctx.Bind(&credentials) 17 | 18 | /*dbJson, _ := os.ReadFile("./database/users.json") 19 | var dbContent entities.Users 20 | json.Unmarshal(dbJson, &dbContent) 21 | 22 | for i := 0; i < len(dbContent.Users); i++ { 23 | // if credentials match, create session 24 | if dbContent.Users[i].Username == credentials.Username && dbContent.Users[i].Password == credentials.Password { 25 | session := createSessionCookie(credentials.Username) 26 | ctx.SetCookie("session", session, 6*60*60, "/", "localhost", false, true) 27 | ctx.JSON(http.StatusOK, gin.H{ 28 | "message": "Login successful", 29 | }) 30 | return 31 | } 32 | }*/ 33 | 34 | allUsers := database.ReadUsers(database.GetOpenedDB()) 35 | for i := 0; i < len(allUsers.Users); i++ { 36 | if allUsers.Users[i].Username == credentials.Username && allUsers.Users[i].Password == credentials.Password { 37 | session := createSessionCookie(credentials.Username) 38 | // CHANGE DOMAIN 39 | ctx.SetCookie("session", session, 6*60*60, "/", "localhost", false, true) 40 | ctx.JSON(http.StatusOK, gin.H{ 41 | "message": "Login successful", 42 | }) 43 | return 44 | } 45 | } 46 | 47 | ctx.JSON(http.StatusUnauthorized, gin.H{ 48 | "message": "Login failed", 49 | }) 50 | } 51 | 52 | func createSessionCookie(username string) string { 53 | 54 | // create new session with all the parameters 55 | var session entities.Session 56 | 57 | id := uuid.New() 58 | now := time.Now() 59 | 60 | session.ID = id.String() 61 | session.Username = username 62 | session.ExpiresIn = now.Unix() + 6*60*60 63 | 64 | returnSession, _ := json.Marshal(session) 65 | 66 | /* 67 | // read session database, append newly created session and write it back 68 | var sessions entities.Sessions 69 | sessionsJson, _ := os.ReadFile("./database/session-cookies.json") 70 | json.Unmarshal(sessionsJson, &sessions) 71 | 72 | sessions.Sessions = append(sessions.Sessions, session) 73 | 74 | writableJson, _ := json.MarshalIndent(sessions, "", "\t") 75 | os.WriteFile("./database/session-cookies.json", writableJson, 0600)*/ 76 | 77 | database.WriteSession(database.GetOpenedDB(), session) 78 | 79 | return string(returnSession) 80 | } 81 | 82 | func isExpired(sessionTime int64) bool { 83 | now := time.Now() 84 | 85 | return sessionTime < now.Unix() 86 | } 87 | -------------------------------------------------------------------------------- /authentication/register.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/Fabucik/ctf-portal/database" 8 | "github.com/Fabucik/ctf-portal/entities" 9 | "github.com/gin-gonic/gin" 10 | "github.com/gin-gonic/gin/binding" 11 | ) 12 | 13 | func Register(ctx *gin.Context) { 14 | var newUser entities.User 15 | ctx.ShouldBindBodyWith(&newUser, binding.JSON) 16 | 17 | var session entities.Session 18 | cookie, _ := ctx.Cookie("session") 19 | json.Unmarshal([]byte(cookie), &session) 20 | 21 | isAdmin := IsAdmin(ctx, session) 22 | if !isAdmin { 23 | return 24 | } 25 | 26 | if DoesUserExists(newUser.Username) || newUser.Username == "admin" { 27 | ctx.JSON(http.StatusConflict, gin.H{ 28 | "message": "Username already exists", 29 | }) 30 | 31 | return 32 | } 33 | /* 34 | // read user database, append new user and write back 35 | userJson, _ := os.ReadFile("./database/users.json") 36 | var users entities.Users 37 | json.Unmarshal(userJson, &users) 38 | 39 | users.Users = append(users.Users, newUser) 40 | 41 | writableJson, _ := json.MarshalIndent(users, "", "\t") 42 | os.WriteFile("./database/users.json", writableJson, 0600) 43 | 44 | // create entries in point database for the newly created user 45 | var userPoints entities.TeamPoints 46 | userPoints.Team = newUser.Username 47 | userPoints.PointAmount = 0 48 | 49 | var db entities.AllPoints 50 | pointDb, _ := os.ReadFile("./database/points.json") 51 | json.Unmarshal(pointDb, &db) 52 | 53 | db.Points = append(db.Points, userPoints) 54 | writableJson, _ = json.MarshalIndent(db, "", "\t") 55 | os.WriteFile("./database/points.json", writableJson, 0600)*/ 56 | 57 | database.CreateUser(database.GetOpenedDB(), newUser) 58 | 59 | var teamPoints entities.TeamPoints 60 | teamPoints.Team = newUser.Username 61 | teamPoints.PointAmount = 0 62 | teamPoints.Solved = "" 63 | database.CreateTeamPoints(database.GetOpenedDB(), teamPoints) 64 | 65 | ctx.JSON(http.StatusOK, gin.H{ 66 | "message": "Register successful", 67 | }) 68 | } 69 | 70 | func DoesUserExists(username string) bool { 71 | /* 72 | dbJson, _ := os.ReadFile("./database/users.json") 73 | 74 | var users entities.Users 75 | json.Unmarshal(dbJson, &users) 76 | 77 | for i := 0; i < len(users.Users); i++ { 78 | if users.Users[i].Username == username { 79 | return true 80 | } 81 | }*/ 82 | 83 | allUsers := database.ReadUsers(database.GetOpenedDB()) 84 | for i := 0; i < len(allUsers.Users); i++ { 85 | if allUsers.Users[i].Username == username { 86 | return true 87 | } 88 | } 89 | 90 | return false 91 | } 92 | -------------------------------------------------------------------------------- /backup/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FADAMIS/ctf-portal/198ac2fdda1c8e6a33ad3272ca3703acb8c42bb4/backup/.gitignore -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FADAMIS/ctf-portal/198ac2fdda1c8e6a33ad3272ca3703acb8c42bb4/bin/.gitignore -------------------------------------------------------------------------------- /ctfsrc/announcement.go: -------------------------------------------------------------------------------- 1 | package ctfsrc 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/Fabucik/ctf-portal/authentication" 8 | "github.com/Fabucik/ctf-portal/database" 9 | "github.com/Fabucik/ctf-portal/entities" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func CreateAnnouncement(ctx *gin.Context) { 14 | var session entities.Session 15 | cookie, _ := ctx.Cookie("session") 16 | json.Unmarshal([]byte(cookie), &session) 17 | 18 | if !authentication.IsAdmin(ctx, session) { 19 | return 20 | } 21 | 22 | var announcement entities.Announcement 23 | ctx.Bind(&announcement) 24 | 25 | if DoesAnnouncementExist(announcement) { 26 | ctx.JSON(http.StatusConflict, gin.H{ 27 | "message": "Announcement with this ID already exists", 28 | }) 29 | 30 | return 31 | } 32 | 33 | /* 34 | var announcements entities.Announcements 35 | announcementDb, _ := os.ReadFile("./database/announcements.json") 36 | json.Unmarshal(announcementDb, &announcements) 37 | 38 | announcements.Announcements = append(announcements.Announcements, announcement) 39 | writableJson, _ := json.MarshalIndent(announcements, "", "\t") 40 | os.WriteFile("./database/announcements.json", writableJson, 0600)*/ 41 | 42 | database.CreateAnnouncement(database.GetOpenedDB(), announcement) 43 | 44 | ctx.JSON(http.StatusOK, gin.H{ 45 | "message": "Announcement successfully created", 46 | }) 47 | } 48 | 49 | func GetAnnouncements(ctx *gin.Context) { 50 | var session entities.Session 51 | cookie, _ := ctx.Cookie("session") 52 | json.Unmarshal([]byte(cookie), &session) 53 | 54 | if !authentication.IsValidSession(session) { 55 | ctx.JSON(http.StatusUnauthorized, gin.H{ 56 | "message": "not logged in", 57 | }) 58 | return 59 | } 60 | 61 | /* 62 | var announcements entities.Announcements 63 | announcementDb, _ := os.ReadFile("./database/announcements.json") 64 | json.Unmarshal(announcementDb, &announcements)*/ 65 | 66 | announcements := database.ReadAnnouncements(database.GetOpenedDB()) 67 | 68 | ctx.JSON(http.StatusOK, announcements) 69 | } 70 | 71 | func DeleteAnnouncement(ctx *gin.Context) { 72 | var session entities.Session 73 | cookie, _ := ctx.Cookie("session") 74 | json.Unmarshal([]byte(cookie), &session) 75 | 76 | if !authentication.IsAdmin(ctx, session) { 77 | return 78 | } 79 | 80 | var announcementToDelete entities.Announcement 81 | ctx.Bind(&announcementToDelete) 82 | 83 | /* 84 | var announcements entities.Announcements 85 | announcementDb, _ := os.ReadFile("./database/announcements.json") 86 | json.Unmarshal(announcementDb, &announcements) 87 | 88 | for i := 0; i < len(announcements.Announcements); i++ { 89 | if announcementToDelete.ID == announcements.Announcements[i].ID { 90 | announcements.Announcements = append(announcements.Announcements[:i], announcements.Announcements[i+1:]...) 91 | } 92 | } 93 | 94 | writableJson, _ := json.MarshalIndent(&announcements, "", "\t") 95 | os.WriteFile("./database/announcements.json", writableJson, 0600)*/ 96 | 97 | database.DeleteAnnouncement(database.GetOpenedDB(), announcementToDelete) 98 | 99 | ctx.JSON(http.StatusOK, gin.H{ 100 | "message": "Successfully deleted announcement", 101 | }) 102 | } 103 | 104 | func DoesAnnouncementExist(announcement entities.Announcement) bool { 105 | /*var announcements entities.Announcements 106 | announcementDb, _ := os.ReadFile("./database/announcements.json") 107 | json.Unmarshal(announcementDb, &announcements)*/ 108 | 109 | announcements := database.ReadAnnouncements(database.GetOpenedDB()) 110 | 111 | for i := 0; i < len(announcements.Announcements); i++ { 112 | if announcement.ID == announcements.Announcements[i].ID { 113 | return true 114 | } 115 | } 116 | 117 | return false 118 | } 119 | -------------------------------------------------------------------------------- /ctfsrc/challengeupload.go: -------------------------------------------------------------------------------- 1 | package ctfsrc 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/Fabucik/ctf-portal/authentication" 13 | "github.com/Fabucik/ctf-portal/entities" 14 | "github.com/gin-gonic/gin" 15 | "github.com/gin-gonic/gin/binding" 16 | ) 17 | 18 | func CreateChallenge(ctx *gin.Context) { 19 | var challenge entities.Challenge 20 | ctx.ShouldBindBodyWith(&challenge, binding.JSON) 21 | 22 | var session entities.Session 23 | cookie, _ := ctx.Cookie("session") 24 | json.Unmarshal([]byte(cookie), &session) 25 | 26 | isAdmin := authentication.IsAdmin(ctx, session) 27 | if !isAdmin { 28 | return 29 | } 30 | 31 | if strings.Contains(challenge.Name, ";") { 32 | ctx.JSON(http.StatusForbidden, gin.H{ 33 | "message": "Cannot use ';' in challenge name", 34 | }) 35 | 36 | return 37 | } 38 | 39 | entries, _ := os.ReadDir("CTFCONTENTS") 40 | for _, e := range entries { 41 | if challenge.Name == e.Name() { 42 | ctx.JSON(http.StatusConflict, gin.H{ 43 | "message": "challenge's name is taken", 44 | }) 45 | 46 | return 47 | } 48 | } 49 | 50 | // all ctf challenges are stored under CTFCONTENTS directory as other directories 51 | os.Mkdir("CTFCONTENTS/"+challenge.Name, 0777) 52 | os.Mkdir("CTFCONTENTS/"+challenge.Name+"/FILES", 0777) 53 | 54 | os.WriteFile("CTFCONTENTS/"+challenge.Name+"/FLAG.TXT", []byte(challenge.Flag), 0600) 55 | os.WriteFile("CTFCONTENTS/"+challenge.Name+"/POINTS.TXT", []byte(strconv.Itoa(challenge.Points)), 0600) 56 | os.WriteFile("CTFCONTENTS/"+challenge.Name+"/DESCRIPTION.TXT", []byte(challenge.Description), 0600) 57 | os.WriteFile("CTFCONTENTS/"+challenge.Name+"/COUNTRY.TXT", []byte(challenge.CountryCode), 0600) 58 | 59 | // write each file to FILES dir 60 | for i := 0; i < len(challenge.Files); i++ { 61 | contents, err := base64.StdEncoding.DecodeString(challenge.Files[i].Base64) 62 | if err != nil { 63 | fmt.Println(err) 64 | } 65 | 66 | f, _ := os.Create("CTFCONTENTS/" + challenge.Name + "/FILES/" + challenge.Files[i].FileName) 67 | defer f.Close() 68 | 69 | f.Write(contents) 70 | } 71 | 72 | ctx.JSON(http.StatusOK, gin.H{ 73 | "message": "Upload successful", 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /ctfsrc/flagvalidation.go: -------------------------------------------------------------------------------- 1 | package ctfsrc 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "os" 7 | "strconv" 8 | 9 | "github.com/Fabucik/ctf-portal/authentication" 10 | "github.com/Fabucik/ctf-portal/entities" 11 | "github.com/gin-gonic/gin" 12 | "github.com/gin-gonic/gin/binding" 13 | ) 14 | 15 | func ValidateFlag(ctx *gin.Context) { 16 | var flag entities.Flag 17 | ctx.ShouldBindBodyWith(&flag, binding.JSON) 18 | 19 | var session entities.Session 20 | cookie, _ := ctx.Cookie("session") 21 | json.Unmarshal([]byte(cookie), &session) 22 | 23 | if !authentication.IsValidSession(session) { 24 | ctx.JSON(http.StatusUnauthorized, gin.H{ 25 | "message": "Not logged in", 26 | }) 27 | 28 | return 29 | } 30 | 31 | if !IsCtfStarted() || IsCtfExpired() { 32 | ctx.JSON(http.StatusUnauthorized, gin.H{ 33 | "message": "CTF is not yet started", 34 | }) 35 | 36 | return 37 | } 38 | 39 | // return not found if challenge (directory) doesnt exist 40 | _, err := os.ReadDir("CTFCONTENTS/" + flag.Challenge) 41 | if err != nil { 42 | ctx.JSON(http.StatusNotFound, gin.H{ 43 | "message": "Challenge not found", 44 | }) 45 | 46 | return 47 | } 48 | 49 | // read flag and compare it with input 50 | serverFlag, _ := os.ReadFile("CTFCONTENTS/" + flag.Challenge + "/FLAG.TXT") 51 | if string(serverFlag) != flag.Value { 52 | ctx.JSON(http.StatusConflict, gin.H{ 53 | "message": "Wrong flag", 54 | }) 55 | 56 | return 57 | } 58 | 59 | // assign points 60 | points, _ := os.ReadFile("CTFCONTENTS/" + flag.Challenge + "/POINTS.TXT") 61 | pointsInt, _ := strconv.Atoi(string(points)) 62 | 63 | // if flag was already answered, return status forbidden 64 | isSuccess := AssignPoints(pointsInt, session.Username, flag.Challenge) 65 | if !isSuccess { 66 | ctx.JSON(http.StatusForbidden, gin.H{ 67 | "message": "Already answered", 68 | }) 69 | 70 | return 71 | } 72 | 73 | ctx.JSON(http.StatusOK, gin.H{ 74 | "message": "Correct", 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /ctfsrc/getchallenge.go: -------------------------------------------------------------------------------- 1 | package ctfsrc 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "net/http" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/Fabucik/ctf-portal/authentication" 11 | "github.com/Fabucik/ctf-portal/entities" 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func GetChallenges(ctx *gin.Context) { 16 | var session entities.Session 17 | cookie, _ := ctx.Cookie("session") 18 | json.Unmarshal([]byte(cookie), &session) 19 | 20 | if !authentication.IsValidSession(session) { 21 | ctx.JSON(http.StatusUnauthorized, gin.H{ 22 | "message": "Not logged in", 23 | }) 24 | 25 | return 26 | } 27 | 28 | if !IsCtfStarted() || IsCtfExpired() { 29 | ctx.JSON(http.StatusUnauthorized, gin.H{ 30 | "message": "CTF is not yet started", 31 | }) 32 | 33 | return 34 | } 35 | 36 | var allChallenges entities.ReturnChallenges 37 | 38 | challengeDirs, _ := os.ReadDir("CTFCONTENTS") 39 | allChallenges.Challenges = make([]entities.ReturnChallenge, len(challengeDirs)) 40 | 41 | for i, entry := range challengeDirs { 42 | points, _ := os.ReadFile("CTFCONTENTS/" + entry.Name() + "/POINTS.TXT") 43 | description, _ := os.ReadFile("CTFCONTENTS/" + entry.Name() + "/DESCRIPTION.TXT") 44 | country, _ := os.ReadFile("CTFCONTENTS/" + entry.Name() + "/COUNTRY.TXT") 45 | 46 | allChallenges.Challenges[i].Name = entry.Name() 47 | allChallenges.Challenges[i].Points, _ = strconv.Atoi(string(points)) 48 | allChallenges.Challenges[i].Description = string(description) 49 | allChallenges.Challenges[i].CountryCode = string(country) 50 | 51 | challengeFiles, _ := os.ReadDir("CTFCONTENTS/" + entry.Name() + "/FILES") 52 | for _, file := range challengeFiles { 53 | contents, _ := os.ReadFile("CTFCONTENTS/" + entry.Name() + "/FILES/" + file.Name()) 54 | 55 | var fileInfo entities.ChallengeFile 56 | fileInfo.FileName = file.Name() 57 | fileInfo.Base64 = base64.StdEncoding.EncodeToString(contents) 58 | allChallenges.Challenges[i].Files = append(allChallenges.Challenges[i].Files, fileInfo) 59 | } 60 | } 61 | 62 | ctx.JSON(http.StatusOK, allChallenges) 63 | } 64 | -------------------------------------------------------------------------------- /ctfsrc/imexport.go: -------------------------------------------------------------------------------- 1 | package ctfsrc 2 | 3 | import ( 4 | "archive/zip" 5 | "encoding/base64" 6 | "encoding/json" 7 | "io" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | "time" 13 | 14 | "github.com/Fabucik/ctf-portal/authentication" 15 | "github.com/Fabucik/ctf-portal/entities" 16 | "github.com/gin-gonic/gin" 17 | ) 18 | 19 | func Export(ctx *gin.Context) { 20 | var session entities.Session 21 | cookie, _ := ctx.Cookie("session") 22 | json.Unmarshal([]byte(cookie), &session) 23 | 24 | if !authentication.IsAdmin(ctx, session) { 25 | return 26 | } 27 | 28 | fileName, err := Zip() 29 | if err != nil { 30 | ctx.JSON(http.StatusInternalServerError, gin.H{ 31 | "message": "An error occured while exporting the CTF", 32 | }) 33 | 34 | return 35 | } 36 | 37 | zipFile, _ := os.ReadFile("./backup/" + fileName) 38 | encoded := base64.StdEncoding.EncodeToString(zipFile) 39 | 40 | ctx.JSON(http.StatusOK, gin.H{ 41 | "message": "OK", 42 | "base64": encoded, 43 | }) 44 | } 45 | 46 | func Import(ctx *gin.Context) { 47 | var session entities.Session 48 | cookie, _ := ctx.Cookie("session") 49 | json.Unmarshal([]byte(cookie), &session) 50 | 51 | if !authentication.IsAdmin(ctx, session) { 52 | return 53 | } 54 | 55 | var encoded entities.Backup 56 | ctx.Bind(&encoded) 57 | 58 | decoded, _ := base64.StdEncoding.DecodeString(encoded.Base64) 59 | os.WriteFile("./backup/import.zip", decoded, 0600) 60 | 61 | ctfcontents, _ := os.ReadDir("./CTFCONTENTS") 62 | for _, entry := range ctfcontents { 63 | if entry.Name() == ".gitignore" { 64 | continue 65 | } 66 | 67 | os.RemoveAll("./CTFCONTENTS/" + entry.Name()) 68 | } 69 | 70 | err := Unzip("./backup/import.zip") 71 | if err != nil { 72 | ctx.JSON(http.StatusInternalServerError, gin.H{ 73 | "message": "An error occured while importing the CTF", 74 | }) 75 | 76 | return 77 | } 78 | 79 | ctx.JSON(http.StatusOK, gin.H{ 80 | "message": "Successfully imported CTF", 81 | }) 82 | } 83 | 84 | func Unzip(src string) error { 85 | reader, err := zip.OpenReader(src) 86 | if err != nil { 87 | return err 88 | } 89 | 90 | defer reader.Close() 91 | 92 | extract := func(file *zip.File, dst string) error { 93 | rc, err := file.Open() 94 | if err != nil { 95 | return err 96 | } 97 | 98 | defer rc.Close() 99 | 100 | path := filepath.Join(dst, file.Name) 101 | 102 | if file.FileInfo().IsDir() { 103 | os.MkdirAll(path, 0777) 104 | } else { 105 | os.MkdirAll(filepath.Dir(path), 0777) 106 | 107 | f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | defer f.Close() 113 | 114 | _, err = io.Copy(f, rc) 115 | if err != nil { 116 | return err 117 | } 118 | } 119 | 120 | return nil 121 | } 122 | 123 | for _, f := range reader.File { 124 | err := extract(f, "./CTFCONTENTS") 125 | if err != nil { 126 | return err 127 | } 128 | } 129 | 130 | return nil 131 | } 132 | 133 | func Zip() (string, error) { 134 | fileName := "backup-" + time.Now().Format("02_01_2006__15_04_05") + ".zip" 135 | dstFile, err := os.Create("./backup/" + fileName) 136 | if err != nil { 137 | return "", err 138 | } 139 | 140 | zipped := zip.NewWriter(dstFile) 141 | err = filepath.Walk("./CTFCONTENTS", func(filePath string, info os.FileInfo, err error) error { 142 | if info.IsDir() { 143 | return nil 144 | } 145 | if err != nil { 146 | return err 147 | } 148 | 149 | relPath := strings.TrimPrefix(filePath, "CTFCONTENTS") 150 | zipFile, err := zipped.Create(relPath) 151 | if err != nil { 152 | return err 153 | } 154 | 155 | fsFile, err := os.Open(filePath) 156 | if err != nil { 157 | return err 158 | } 159 | 160 | _, err = io.Copy(zipFile, fsFile) 161 | if err != nil { 162 | return err 163 | } 164 | 165 | return nil 166 | }) 167 | 168 | if err != nil { 169 | return "", err 170 | } 171 | 172 | zipped.Close() 173 | 174 | return fileName, nil 175 | } 176 | -------------------------------------------------------------------------------- /ctfsrc/points.go: -------------------------------------------------------------------------------- 1 | package ctfsrc 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/Fabucik/ctf-portal/authentication" 9 | "github.com/Fabucik/ctf-portal/database" 10 | "github.com/Fabucik/ctf-portal/entities" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func AssignPoints(points int, team string, challenge string) bool { 15 | /* var db entities.AllPoints 16 | dbJson, _ := os.ReadFile("./database/points.json") 17 | json.Unmarshal(dbJson, &db) 18 | 19 | var newPoints entities.TeamPoints 20 | newPoints.Team = team 21 | for i := 0; i < len(db.Points); i++ { 22 | if newPoints.Team == db.Points[i].Team { 23 | // if team already answered the flag return from the function 24 | for j := 0; j < len(db.Points[i].Solved); j++ { 25 | if db.Points[i].Solved[j] == challenge { 26 | return false 27 | } 28 | } 29 | 30 | // update finished flags 31 | newPoints.Solved = append(db.Points[i].Solved, challenge) 32 | 33 | // update points and delete old entry 34 | newPoints.PointAmount = db.Points[i].PointAmount + points 35 | db.Points = append(db.Points[:i], db.Points[i+1:]...) 36 | } 37 | } 38 | 39 | db.Points = append(db.Points, newPoints) 40 | 41 | writableJson, _ := json.MarshalIndent(db, "", "\t") 42 | os.WriteFile("./database/points.json", writableJson, 0600) 43 | 44 | return true*/ 45 | 46 | teamPoints := database.ReadTeamPoints(database.GetOpenedDB(), team) 47 | solved := strings.Split(teamPoints.Solved, ";") 48 | for i := 0; i < len(solved); i++ { 49 | if challenge == solved[i] { 50 | return false 51 | } 52 | } 53 | 54 | teamPoints.Team = team 55 | teamPoints.Solved += ";" + challenge 56 | teamPoints.PointAmount += points 57 | database.UpdatePoints(database.GetOpenedDB(), teamPoints) 58 | 59 | return true 60 | } 61 | 62 | func GetAllPoints(ctx *gin.Context) { 63 | var session entities.Session 64 | cookie, _ := ctx.Cookie("session") 65 | json.Unmarshal([]byte(cookie), &session) 66 | 67 | if !authentication.IsValidSession(session) { 68 | ctx.JSON(http.StatusUnauthorized, gin.H{ 69 | "message": "Not logged in", 70 | }) 71 | 72 | return 73 | } 74 | /* 75 | var allPoints entities.AllPoints 76 | pointDb, _ := os.ReadFile("./database/points.json") 77 | json.Unmarshal(pointDb, &allPoints) 78 | 79 | ctx.JSON(http.StatusOK, allPoints)*/ 80 | 81 | allPoints := database.ReadAllPoints(database.GetOpenedDB()) 82 | ctx.JSON(http.StatusOK, allPoints) 83 | } 84 | -------------------------------------------------------------------------------- /ctfsrc/startstop.go: -------------------------------------------------------------------------------- 1 | package ctfsrc 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/Fabucik/ctf-portal/authentication" 10 | "github.com/Fabucik/ctf-portal/database" 11 | "github.com/Fabucik/ctf-portal/entities" 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func SetTime(ctx *gin.Context) { 16 | var session entities.Session 17 | cookie, _ := ctx.Cookie("session") 18 | json.Unmarshal([]byte(cookie), &session) 19 | 20 | if !authentication.IsAdmin(ctx, session) { 21 | return 22 | } 23 | 24 | var autoStart entities.AutomaticStart 25 | ctx.Bind(&autoStart) 26 | 27 | /* 28 | var startInfo entities.StartStopInfo 29 | startInfoDb, _ := os.ReadFile("./database/time.json") 30 | json.Unmarshal(startInfoDb, &startInfo) 31 | 32 | 33 | startInfo.Automatic = ctfTime 34 | startInfo.Automatic.IsValid = true 35 | startInfo.Manual.IsValid = false 36 | 37 | writableJson, _ := json.MarshalIndent(startInfo, "", "\t") 38 | os.WriteFile("./database/time.json", writableJson, 0600)*/ 39 | 40 | var manualStart entities.ManualStart 41 | manualStart.IsValid = false 42 | autoStart.IsValid = true 43 | 44 | database.WriteAutoStart(database.GetOpenedDB(), autoStart) 45 | database.WriteManStart(database.GetOpenedDB(), manualStart) 46 | 47 | ctx.JSON(http.StatusOK, gin.H{ 48 | "message": "Successfully set time", 49 | }) 50 | } 51 | 52 | func GetTime(ctx *gin.Context) { 53 | var session entities.Session 54 | cookie, _ := ctx.Cookie("session") 55 | json.Unmarshal([]byte(cookie), &session) 56 | 57 | if !authentication.IsValidSession(session) { 58 | ctx.JSON(http.StatusUnauthorized, gin.H{ 59 | "message": "Not logged in", 60 | }) 61 | 62 | return 63 | } 64 | 65 | /* 66 | var ctfTime entities.StartStopInfo 67 | timeDb, _ := os.ReadFile("./database/time.json") 68 | json.Unmarshal(timeDb, &ctfTime)*/ 69 | 70 | ctfTime := database.ReadAutoStart(database.GetOpenedDB()) 71 | 72 | ctx.JSON(http.StatusOK, ctfTime) 73 | } 74 | 75 | func SetManualStartStop(ctx *gin.Context) { 76 | var session entities.Session 77 | cookie, _ := ctx.Cookie("session") 78 | json.Unmarshal([]byte(cookie), &session) 79 | 80 | if !authentication.IsAdmin(ctx, session) { 81 | return 82 | } 83 | 84 | var manualStart entities.ManualStart 85 | ctx.Bind(&manualStart) 86 | 87 | /* 88 | var startInfo entities.StartStopInfo 89 | startInfoDb, _ := os.ReadFile("./database/time.json") 90 | json.Unmarshal(startInfoDb, &startInfo) 91 | 92 | startInfo.Manual = manualStart 93 | startInfo.Manual.IsValid = true 94 | startInfo.Automatic.IsValid = false 95 | 96 | writableJson, _ := json.MarshalIndent(&startInfo, "", "\t") 97 | os.WriteFile("./database/time.json", writableJson, 0600)*/ 98 | 99 | var autoStart entities.AutomaticStart 100 | autoStart.IsValid = false 101 | manualStart.IsValid = true 102 | 103 | database.WriteManStart(database.GetOpenedDB(), manualStart) 104 | database.WriteAutoStart(database.GetOpenedDB(), autoStart) 105 | 106 | ctx.JSON(http.StatusOK, gin.H{ 107 | "message": "Successfully set time", 108 | "isrunning": strconv.FormatBool(manualStart.IsStarted), 109 | }) 110 | } 111 | 112 | func GetManualStartStop(ctx *gin.Context) { 113 | var session entities.Session 114 | cookie, _ := ctx.Cookie("session") 115 | json.Unmarshal([]byte(cookie), &session) 116 | 117 | if !authentication.IsValidSession(session) { 118 | ctx.JSON(http.StatusUnauthorized, gin.H{ 119 | "message": "Not logged in", 120 | }) 121 | 122 | return 123 | } 124 | 125 | /* 126 | var ctfTime entities.ManualStart 127 | startDb, _ := os.ReadFile("./database/time.json") 128 | json.Unmarshal(startDb, &ctfTime)*/ 129 | 130 | manTime := database.ReadManStart(database.GetOpenedDB()) 131 | 132 | ctx.JSON(http.StatusOK, manTime) 133 | } 134 | 135 | func IsCtfExpired() bool { 136 | /* 137 | var startInfo entities.StartStopInfo 138 | timeDb, _ := os.ReadFile("./database/time.json") 139 | json.Unmarshal(timeDb, &startInfo)*/ 140 | 141 | autoStart := database.ReadAutoStart(database.GetOpenedDB()) 142 | manStart := database.ReadManStart(database.GetOpenedDB()) 143 | 144 | // if automatic start is used 145 | if autoStart.IsValid { 146 | now := time.Now() 147 | return autoStart.StopTime < now.Unix() 148 | } 149 | 150 | // if manual start is used 151 | return !manStart.IsStarted 152 | } 153 | 154 | func IsCtfStarted() bool { 155 | /* 156 | var startInfo entities.StartStopInfo 157 | timeDb, _ := os.ReadFile("./database/time.json") 158 | json.Unmarshal(timeDb, &startInfo)*/ 159 | 160 | autoStart := database.ReadAutoStart(database.GetOpenedDB()) 161 | manStart := database.ReadManStart(database.GetOpenedDB()) 162 | 163 | // if automatic start is used 164 | if autoStart.IsValid { 165 | now := time.Now() 166 | return autoStart.StartTime < now.Unix() 167 | } 168 | 169 | // if manual start is used 170 | return manStart.IsStarted 171 | } 172 | -------------------------------------------------------------------------------- /ctfsrc/teammanagement.go: -------------------------------------------------------------------------------- 1 | package ctfsrc 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/Fabucik/ctf-portal/authentication" 8 | "github.com/Fabucik/ctf-portal/database" 9 | "github.com/Fabucik/ctf-portal/entities" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func GetTeams(ctx *gin.Context) { 14 | var session entities.Session 15 | cookie, _ := ctx.Cookie("session") 16 | json.Unmarshal([]byte(cookie), &session) 17 | 18 | if !authentication.IsAdmin(ctx, session) { 19 | return 20 | } 21 | /* 22 | var users entities.Users 23 | teamDb, _ := os.ReadFile("./database/users.json") 24 | json.Unmarshal(teamDb, &users)*/ 25 | 26 | users := database.ReadUsers(database.GetOpenedDB()) 27 | 28 | ctx.JSON(http.StatusOK, users) 29 | } 30 | 31 | func DeleteTeam(ctx *gin.Context) { 32 | var session entities.Session 33 | cookie, _ := ctx.Cookie("session") 34 | json.Unmarshal([]byte(cookie), &session) 35 | 36 | if !authentication.IsAdmin(ctx, session) { 37 | return 38 | } 39 | 40 | var userToDelete entities.User 41 | ctx.Bind(&userToDelete) 42 | 43 | /* 44 | var users entities.Users 45 | userDb, _ := os.ReadFile("./database/users.json") 46 | json.Unmarshal(userDb, &users) 47 | 48 | var points entities.AllPoints 49 | pointDb, _ := os.ReadFile("./database/points.json") 50 | json.Unmarshal(pointDb, &points) 51 | 52 | var sessions entities.Sessions 53 | sessionDb, _ := os.ReadFile("./database/session-cookies.json") 54 | json.Unmarshal(sessionDb, &sessions) 55 | 56 | check := 0 57 | for i := 0; i < len(users.Users); i++ { 58 | // cannot delete admin user 59 | if userToDelete.Username == "admin" { 60 | ctx.JSON(http.StatusForbidden, gin.H{ 61 | "message": "cannot delete admin", 62 | }) 63 | } 64 | 65 | if userToDelete.Username == users.Users[i].Username { 66 | users.Users = append(users.Users[:i], users.Users[i+1:]...) 67 | check++ 68 | } 69 | } 70 | 71 | if check == 0 { 72 | ctx.JSON(http.StatusConflict, gin.H{ 73 | "message": "User does not exist", 74 | }) 75 | 76 | return 77 | } 78 | 79 | for i := 0; i < len(points.Points); i++ { 80 | if userToDelete.Username == points.Points[i].Team { 81 | points.Points = append(points.Points[:i], points.Points[i+1:]...) 82 | } 83 | } 84 | 85 | for i := 0; i < len(sessions.Sessions); i++ { 86 | if userToDelete.Username == sessions.Sessions[i].Username { 87 | sessions.Sessions = append(sessions.Sessions[:i], sessions.Sessions[i+1:]...) 88 | } 89 | } 90 | 91 | writableUserJson, _ := json.MarshalIndent(&users, "", "\t") 92 | os.WriteFile("./database/users.json", writableUserJson, 0600) 93 | 94 | writablePointJson, _ := json.MarshalIndent(&points, "", "\t") 95 | os.WriteFile("./database/points.json", writablePointJson, 0600) 96 | 97 | writableSessionJson, _ := json.MarshalIndent(&sessions, "", "\t") 98 | os.WriteFile("./database/session-cookies.json", writableSessionJson, 0600)*/ 99 | 100 | if userToDelete.Username == "admin" { 101 | ctx.JSON(http.StatusForbidden, gin.H{ 102 | "message": "cannot delete admin", 103 | }) 104 | 105 | return 106 | } 107 | 108 | if !authentication.DoesUserExists(userToDelete.Username) { 109 | ctx.JSON(http.StatusConflict, gin.H{ 110 | "message": "User does not exist", 111 | }) 112 | 113 | return 114 | } 115 | 116 | database.DeleteUser(database.GetOpenedDB(), userToDelete) 117 | 118 | ctx.JSON(http.StatusOK, gin.H{ 119 | "message": "OK", 120 | }) 121 | } 122 | -------------------------------------------------------------------------------- /database/db.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "github.com/Fabucik/ctf-portal/entities" 5 | "gorm.io/driver/postgres" 6 | "gorm.io/gorm" 7 | ) 8 | 9 | func InitDB() (*gorm.DB, error) { 10 | dsn := "host=localhost user=fanda dbname=ctfportal port=5432 sslmode=disable TimeZone=Europe/Prague" 11 | db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | db.AutoMigrate(&entities.User{}, &entities.Announcement{}, &entities.Session{}, &entities.TeamPoints{}, &entities.AutomaticStart{}, &entities.ManualStart{}) 17 | 18 | return db, nil 19 | } 20 | 21 | var DB, _ = InitDB() 22 | 23 | func GetOpenedDB() *gorm.DB { 24 | return DB 25 | } 26 | 27 | func CreateUser(db *gorm.DB, user entities.User) { 28 | db.Create(&user) 29 | } 30 | 31 | func UpdateUser(db *gorm.DB, user entities.User) { 32 | db.Save(&user) 33 | } 34 | 35 | func ReadUsers(db *gorm.DB) entities.Users { 36 | var users []entities.User 37 | db.Find(&users) 38 | 39 | var allUsers entities.Users 40 | allUsers.Users = users 41 | return allUsers 42 | } 43 | 44 | func DeleteUser(db *gorm.DB, user entities.User) { 45 | db.Delete(user) 46 | db.Delete(&entities.Session{}, "username = ?", user.Username) 47 | db.Delete(&entities.TeamPoints{}, "team = ?", user.Username) 48 | } 49 | 50 | func CreateAnnouncement(db *gorm.DB, announcement entities.Announcement) { 51 | db.Create(&announcement) 52 | } 53 | 54 | func ReadAnnouncements(db *gorm.DB) entities.Announcements { 55 | var announcements []entities.Announcement 56 | 57 | db.Find(&announcements) 58 | 59 | var allAnnouncements entities.Announcements 60 | allAnnouncements.Announcements = announcements 61 | 62 | return allAnnouncements 63 | } 64 | 65 | func DeleteAnnouncement(db *gorm.DB, announcement entities.Announcement) { 66 | db.Delete(&announcement) 67 | } 68 | 69 | func WriteSession(db *gorm.DB, session entities.Session) { 70 | db.Create(&session) 71 | } 72 | 73 | func ReadSessions(db *gorm.DB) entities.Sessions { 74 | var sessions []entities.Session 75 | 76 | db.Find(&sessions) 77 | 78 | var allSessions entities.Sessions 79 | allSessions.Sessions = sessions 80 | return allSessions 81 | } 82 | 83 | func CreateTeamPoints(db *gorm.DB, teamPoints entities.TeamPoints) { 84 | db.Create(&teamPoints) 85 | } 86 | 87 | // solved will be string split by ; 88 | func UpdatePoints(db *gorm.DB, teamPoints entities.TeamPoints) { 89 | db.Save(&teamPoints) 90 | } 91 | 92 | func ReadAllPoints(db *gorm.DB) entities.AllPoints { 93 | var points []entities.TeamPoints 94 | 95 | db.Find(&points) 96 | 97 | var allPoints entities.AllPoints 98 | allPoints.Points = points 99 | return allPoints 100 | } 101 | 102 | func ReadTeamPoints(db *gorm.DB, team string) entities.TeamPoints { 103 | var points entities.TeamPoints 104 | 105 | db.Last(&points, "team = ?", team) 106 | 107 | return points 108 | } 109 | 110 | func WriteAutoStart(db *gorm.DB, time entities.AutomaticStart) { 111 | db.Save(&time) 112 | } 113 | 114 | func ReadAutoStart(db *gorm.DB) entities.AutomaticStart { 115 | var start entities.AutomaticStart 116 | 117 | db.Last(&start) 118 | 119 | return start 120 | } 121 | 122 | func WriteManStart(db *gorm.DB, time entities.ManualStart) { 123 | db.Save(&time) 124 | } 125 | 126 | func ReadManStart(db *gorm.DB) entities.ManualStart { 127 | var start entities.ManualStart 128 | 129 | db.Last(&start) 130 | 131 | return start 132 | } 133 | -------------------------------------------------------------------------------- /entities/announcements.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Announcement struct { 4 | Message string `json:"message"` 5 | ID int `json:"id"` 6 | } 7 | 8 | type Announcements struct { 9 | Announcements []Announcement `json:"announcements"` 10 | } 11 | -------------------------------------------------------------------------------- /entities/backup.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Backup struct { 4 | Base64 string `json:"base64"` 5 | } 6 | -------------------------------------------------------------------------------- /entities/challenge.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Challenge struct { 4 | Name string `json:"name"` 5 | Files []ChallengeFile `json:"files"` 6 | Flag string `json:"flag"` 7 | Points int `json:"points"` 8 | Description string `json:"description"` 9 | CountryCode string `json:"country"` 10 | } 11 | 12 | type ChallengeFile struct { 13 | FileName string `json:"filename"` 14 | Base64 string `json:"base64"` 15 | } 16 | 17 | type Challenges struct { 18 | Challenges []Challenge `json:"challenges"` 19 | } 20 | 21 | // this will be sent to the client, basically challenge struct stripped by the flag 22 | type ReturnChallenge struct { 23 | Name string `json:"name"` 24 | Files []ChallengeFile `json:"files"` 25 | Points int `json:"points"` 26 | Description string `json:"description"` 27 | CountryCode string `json:"country"` 28 | } 29 | 30 | type ReturnChallenges struct { 31 | Challenges []ReturnChallenge `json:"challenges"` 32 | } 33 | -------------------------------------------------------------------------------- /entities/flag.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Flag struct { 4 | Challenge string `json:"challenge"` 5 | Value string `json:"value"` 6 | } 7 | -------------------------------------------------------------------------------- /entities/points.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type TeamPoints struct { 4 | Team string `json:"team"` 5 | PointAmount int `json:"points"` 6 | Solved string `json:"solved"` 7 | ID int `json:"id"` 8 | } 9 | 10 | type AllPoints struct { 11 | Points []TeamPoints `json:"allpoints"` 12 | } 13 | -------------------------------------------------------------------------------- /entities/sessions.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Session struct { 4 | ID string `json:"id"` 5 | Username string `json:"username"` 6 | ExpiresIn int64 `json:"expiresin"` 7 | } 8 | 9 | type Sessions struct { 10 | Sessions []Session `json:"sessions"` 11 | } 12 | -------------------------------------------------------------------------------- /entities/startstop.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type AutomaticStart struct { 4 | StartTime int64 `json:"starttime"` 5 | StopTime int64 `json:"stoptime"` 6 | // false if manual start is used 7 | IsValid bool `json:"isvalid"` 8 | ID int `json:"id"` 9 | } 10 | 11 | type ManualStart struct { 12 | IsStarted bool `json:"isstarted"` 13 | 14 | // false if automatic start is used 15 | IsValid bool `json:"isvalid"` 16 | ID int `json:"id"` 17 | } 18 | 19 | // this is used to write both of entries to the "database" 20 | type StartStopInfo struct { 21 | Automatic AutomaticStart `json:"automatic"` 22 | Manual ManualStart `json:"manual"` 23 | } 24 | -------------------------------------------------------------------------------- /entities/users.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type User struct { 4 | Username string `json:"username"` 5 | Password string `json:"password"` 6 | ID int `json:"id"` 7 | } 8 | 9 | type Users struct { 10 | Users []User `json:"users"` 11 | } 12 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules 3 | jsconfig.json 4 | package-lock.json 5 | package.json 6 | svelte.config.js 7 | vite.config.js 8 | postcss.config.cjs 9 | tailwind.config.cjs 10 | .vscode/ 11 | -------------------------------------------------------------------------------- /frontend/dist_collected/CTF.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CTF 6 | 7 | 8 | 9 | 10 | 11 |
12 | 244 | 380 |
381 |

382 |
383 |

LEADERBOARD:

384 |
385 |
386 |

ANNOUNCMENTS:

387 |
388 |
389 |
390 |

391 |

392 |

393 |

Challange Description

394 | 395 | 396 | 397 |
398 | 399 | 611 | -------------------------------------------------------------------------------- /frontend/dist_collected/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Svelte 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/dist_collected/assets/index-357cb75c.css: -------------------------------------------------------------------------------- 1 | *,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.mb-1{margin-bottom:.25rem}.mb-3{margin-bottom:.75rem}.ml-10{margin-left:2.5rem}.ml-2{margin-left:.5rem}.ml-5{margin-left:1.25rem}.mr-4{margin-right:1rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-8{margin-top:2rem}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-20{height:5rem}.h-64{height:16rem}.h-9{height:2.25rem}.h-full{height:100%}.h-screen{height:100vh}.w-3\/4{width:75%}.w-32{width:8rem}.w-40{width:10rem}.w-48{width:12rem}.w-52{width:13rem}.w-56{width:14rem}.w-64{width:16rem}.w-7\/12{width:58.333333%}.w-full{width:100%}.w-screen{width:100vw}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.place-content-center{place-content:center}.place-content-start{place-content:start}.place-content-end{place-content:end}.place-items-center{place-items:center}.justify-center{justify-content:center}.overflow-hidden{overflow:hidden}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-violet-500{--tw-border-opacity: 1;border-color:rgb(139 92 246 / var(--tw-border-opacity))}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity))}.bg-violet-700{--tw-bg-opacity: 1;background-color:rgb(109 40 217 / var(--tw-bg-opacity))}.bg-violet-800{--tw-bg-opacity: 1;background-color:rgb(91 33 182 / var(--tw-bg-opacity))}.fill-gray-300{fill:#d1d5db}.p-10{padding:2.5rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.pt-7{padding-top:1.75rem}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity))}.text-gray-50{--tw-text-opacity: 1;color:rgb(249 250 251 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.outline-violet-500{outline-color:#8b5cf6}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hover\:border-4:hover{border-width:4px}.hover\:bg-violet-700:hover{--tw-bg-opacity: 1;background-color:rgb(109 40 217 / var(--tw-bg-opacity))}.hover\:bg-violet-900:hover{--tw-bg-opacity: 1;background-color:rgb(76 29 149 / var(--tw-bg-opacity))}@media (min-width: 1024px){.lg\:pl-64{padding-left:16rem}.lg\:pr-64{padding-right:16rem}}#body.svelte-deiqed{background-color:#161616;opacity:.8;background-size:10px 10px;background-image:repeating-linear-gradient(45deg,#2e2e2e 0,#2e2e2e 1px,#040404 0,#010101 50%)} 2 | -------------------------------------------------------------------------------- /frontend/dist_collected/assets/index-860e7d9b.js: -------------------------------------------------------------------------------- 1 | (function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))a(s);new MutationObserver(s=>{for(const r of s)if(r.type==="childList")for(const h of r.addedNodes)h.tagName==="LINK"&&h.rel==="modulepreload"&&a(h)}).observe(document,{childList:!0,subtree:!0});function l(s){const r={};return s.integrity&&(r.integrity=s.integrity),s.referrerPolicy&&(r.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?r.credentials="include":s.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function a(s){if(s.ep)return;s.ep=!0;const r=l(s);fetch(s.href,r)}})();function J(){}function X(t,e){for(const l in e)t[l]=e[l];return t}function Ye(t){return t()}function De(){return Object.create(null)}function ae(t){t.forEach(Ye)}function Ze(t){return typeof t=="function"}function ge(t,e){return t!=t?e==e:t!==e||t&&typeof t=="object"||typeof t=="function"}function ot(t){return Object.keys(t).length===0}function ze(t){const e={};for(const l in t)l[0]!=="$"&&(e[l]=t[l]);return e}function se(t,e){const l={};e=new Set(e);for(const a in t)!e.has(a)&&a[0]!=="$"&&(l[a]=t[a]);return l}function o(t,e){t.appendChild(e)}function K(t,e,l){t.insertBefore(e,l||null)}function G(t){t.parentNode&&t.parentNode.removeChild(t)}function Me(t,e){for(let l=0;lt.removeEventListener(e,l,a)}function i(t,e,l){l==null?t.removeAttribute(e):t.getAttribute(e)!==l&&t.setAttribute(e,l)}function ie(t,e){for(const l in e)i(t,l,e[l])}function $e(t){return t===""?null:+t}function st(t){return Array.from(t.childNodes)}function et(t,e){e=""+e,t.wholeText!==e&&(t.data=e)}function H(t,e){t.value=e??""}let Be;function ke(t){Be=t}function Te(t,e){const l=t.$$.callbacks[e.type];l&&l.slice().forEach(a=>a.call(this,e))}const we=[],Je=[],Oe=[],Ue=[],it=Promise.resolve();let Le=!1;function at(){Le||(Le=!0,it.then(tt))}function Ne(t){Oe.push(t)}const je=new Set;let be=0;function tt(){if(be!==0)return;const t=Be;do{try{for(;be{Fe.delete(t),a&&(l&&t.d(1),a())}),t.o(e)}else a&&a()}function Se(t,e){const l={},a={},s={$$scope:1};let r=t.length;for(;r--;){const h=t[r],c=e[r];if(c){for(const d in h)d in c||(a[d]=1);for(const d in c)s[d]||(l[d]=c[d],s[d]=1);t[r]=c}else for(const d in h)s[d]=1}for(const h in a)h in l||(l[h]=void 0);return l}function me(t){t&&t.c()}function ce(t,e,l,a){const{fragment:s,after_update:r}=t.$$;s&&s.m(e,l),a||Ne(()=>{const h=t.$$.on_mount.map(Ye).filter(Ze);t.$$.on_destroy?t.$$.on_destroy.push(...h):ae(h),t.$$.on_mount=[]}),r.forEach(Ne)}function ue(t,e){const l=t.$$;l.fragment!==null&&(ae(l.on_destroy),l.fragment&&l.fragment.d(e),l.on_destroy=l.fragment=null,l.ctx=[])}function ft(t,e){t.$$.dirty[0]===-1&&(we.push(t),at(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{const k=w.length?w[0]:_;return n.ctx&&s(n.ctx[x],n.ctx[x]=k)&&(!n.skip_bound&&n.bound[x]&&n.bound[x](k),f&&ft(t,x)),_}):[],n.update(),f=!0,ae(n.before_update),n.fragment=a?a(n.ctx):!1,e.target){if(e.hydrate){const x=st(e.target);n.fragment&&n.fragment.l(x),x.forEach(G)}else n.fragment&&n.fragment.c();e.intro&&oe(t.$$.fragment),ce(t,e.target,e.anchor,e.customElement),tt()}ke(d)}class ve{$destroy(){ue(this,1),this.$destroy=J}$on(e,l){if(!Ze(l))return J;const a=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return a.push(l),()=>{const s=a.indexOf(l);s!==-1&&a.splice(s,1)}}$set(e){this.$$set&&!ot(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}}function dt(t){let e,l,a,s,r,h,c=[{xmlns:"http://www.w3.org/2000/svg"},{viewBox:"0 0 24 24"},{width:t[0]},{height:t[0]},{fill:t[1]},{class:s="remixicon "+t[2]},t[3]],d={};for(let n=0;n{e=X(X({},e),ze(n)),l(3,s=se(e,a)),"size"in n&&l(0,r=n.size),"color"in n&&l(1,h=n.color),"class"in n&&l(2,c=n.class)},[r,h,c,s,d]}class lt extends ve{constructor(e){super(),pe(this,e,ht,dt,ge,{size:0,color:1,class:2})}}function mt(t){let e,l,a,s,r,h,c=[{xmlns:"http://www.w3.org/2000/svg"},{viewBox:"0 0 24 24"},{width:t[0]},{height:t[0]},{fill:t[1]},{class:s="remixicon "+t[2]},t[3]],d={};for(let n=0;n{e=X(X({},e),ze(n)),l(3,s=se(e,a)),"size"in n&&l(0,r=n.size),"color"in n&&l(1,h=n.color),"class"in n&&l(2,c=n.class)},[r,h,c,s,d]}class pt extends ve{constructor(e){super(),pe(this,e,gt,mt,ge,{size:0,color:1,class:2})}}function vt(t){let e,l,a,s,r,h,c=[{xmlns:"http://www.w3.org/2000/svg"},{viewBox:"0 0 24 24"},{width:t[0]},{height:t[0]},{fill:t[1]},{class:s="remixicon "+t[2]},t[3]],d={};for(let n=0;n{e=X(X({},e),ze(n)),l(3,s=se(e,a)),"size"in n&&l(0,r=n.size),"color"in n&&l(1,h=n.color),"class"in n&&l(2,c=n.class)},[r,h,c,s,d]}class _t extends ve{constructor(e){super(),pe(this,e,xt,vt,ge,{size:0,color:1,class:2})}}function bt(t){let e,l,a,s,r,h,c=[{xmlns:"http://www.w3.org/2000/svg"},{viewBox:"0 0 24 24"},{width:t[0]},{height:t[0]},{fill:t[1]},{class:s="remixicon "+t[2]},t[3]],d={};for(let n=0;n{e=X(X({},e),ze(n)),l(3,s=se(e,a)),"size"in n&&l(0,r=n.size),"color"in n&&l(1,h=n.color),"class"in n&&l(2,c=n.class)},[r,h,c,s,d]}class yt extends ve{constructor(e){super(),pe(this,e,wt,bt,ge,{size:0,color:1,class:2})}}function Ct(t){let e,l,a,s,r,h,c=[{xmlns:"http://www.w3.org/2000/svg"},{viewBox:"0 0 24 24"},{width:t[0]},{height:t[0]},{fill:t[1]},{class:s="remixicon "+t[2]},t[3]],d={};for(let n=0;n{e=X(X({},e),ze(n)),l(3,s=se(e,a)),"size"in n&&l(0,r=n.size),"color"in n&&l(1,h=n.color),"class"in n&&l(2,c=n.class)},[r,h,c,s,d]}class zt extends ve{constructor(e){super(),pe(this,e,kt,Ct,ge,{size:0,color:1,class:2})}}function Ve(t,e,l){const a=t.slice();return a[47]=e[l],a}function qe(t,e,l){const a=t.slice();return a[39]=e[l],a}function Ge(t,e,l){const a=t.slice();return a[42]=e[l],a}function Re(t,e,l){const a=t.slice();return a[39]=e[l],a}function Tt(t){let e,l,a,s,r,h,c,d,n,f,x,_,w,k,S,B,z,F,M,P,I,L,D,A,T=t[1],v=[];for(let g=0;gDelete',i(l,"class","text-white text-2xl font-mono"),i(s,"type","text"),i(s,"class","w-full h-10 rounded-xl p-2 bg-gray-800 text-gray-50 flex place-content-center"),s.value=r=t[42].name,s.disabled=!0,i(n,"class","text-white text-2xl font-mono"),i(x,"type","text"),i(x,"class","w-full h-10 rounded-xl p-2 bg-gray-800 text-gray-50 flex place-content-center"),x.value=_=t[42].description,x.disabled=!0,i(S,"class","text-white text-2xl font-mono"),i(z,"type","text"),i(z,"class","w-48 h-10 rounded-xl p-2 bg-gray-800 text-gray-50 flex place-content-center text-center"),z.value=F=t[42].points,z.disabled=!0,i(k,"class","ml-2"),i(c,"class","flex"),i(P,"class","text-white text-xl font-mono mt-3"),i(D,"class","w-full flex place-content-end"),i(e,"class","w-full bg-black border border-violet-500 rounded-xl mt-2 p-10 pt-7")},m(v,g){K(v,e,g),o(e,l),o(e,a),o(e,s),o(e,h),o(e,c),o(c,d),o(d,n),o(d,f),o(d,x),o(c,w),o(c,k),o(k,S),o(k,B),o(k,z),o(e,M),o(e,P),o(e,I);for(let b=0;bAdmin page',s=m(),r=u("div"),h=u("div"),c=u("div"),me(d.$$.fragment),n=m(),f=u("h1"),f.textContent="General",x=m(),_=u("div"),me(w.$$.fragment),k=m(),S=u("h1"),S.textContent="Announcment",B=m(),z=u("div"),me(F.$$.fragment),M=m(),P=u("h1"),P.textContent="Challanges",I=m(),L=u("div"),me(D.$$.fragment),A=m(),T=u("h1"),T.textContent="Teams",v=m(),b&&b.c(),i(a,"id","header"),i(a,"class","grid place-content-center w-screen h-20 bg-black outline outline-violet-500"),i(f,"class","text-xl text-gray-300 font-mono"),i(c,"class","flex w-52 transition-all hover:bg-violet-900 p-3 rounded-xl ml-2 mt-10"),i(S,"class","text-xl text-gray-300 font-mono"),i(_,"class","flex w-52 transition-all hover:bg-violet-900 p-3 rounded-xl ml-2 mt-1"),i(P,"class","text-xl text-gray-300 font-mono"),i(z,"class","flex w-52 transition-all hover:bg-violet-900 p-3 rounded-xl ml-2 mt-1"),i(T,"class","text-xl text-gray-300 font-mono"),i(L,"class","flex w-52 transition-all hover:bg-violet-900 p-3 rounded-xl ml-2 mt-1"),i(h,"class","grid place-content-start w-56 h-screen bg-black"),i(r,"class","flex"),i(l,"id","body"),i(l,"class","w-full h-full overflow-hidden svelte-deiqed")},m(C,Q){K(C,e,Q),o(e,l),o(l,a),o(l,s),o(l,r),o(r,h),o(h,c),ce(d,c,null),o(c,n),o(c,f),o(h,x),o(h,_),ce(w,_,null),o(_,k),o(_,S),o(h,B),o(h,z),ce(F,z,null),o(z,M),o(z,P),o(h,I),o(h,L),ce(D,L,null),o(L,A),o(L,T),o(r,v),~g&&V[g].m(r,null),E=!0,U||(R=[j(c,"click",t[12]),j(_,"click",t[13]),j(z,"click",t[14]),j(L,"click",t[15])],U=!0)},p(C,Q){let Y=g;g=ee(C),g===Y?~g&&V[g].p(C,Q):(b&&(ct(),re(V[Y],1,1,()=>{V[Y]=null}),ut()),~g?(b=V[g],b?b.p(C,Q):(b=V[g]=q[g](C),b.c()),oe(b,1),b.m(r,null)):b=null)},i(C){E||(oe(d.$$.fragment,C),oe(w.$$.fragment,C),oe(F.$$.fragment,C),oe(D.$$.fragment,C),oe(b),E=!0)},o(C){re(d.$$.fragment,C),re(w.$$.fragment,C),re(F.$$.fragment,C),re(D.$$.fragment,C),re(b),E=!1},d(C){C&&G(e),ue(d),ue(w),ue(F),ue(D),~g&&V[g].d(),U=!1,ae(R)}}}let nt=0;function Pt(){document.getElementById("import").click()}function At(){fetch("/manualstart",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isstarted:!0})}).then(t=>{t.status==200?alert("CTF started"):alert("💀")})}function jt(){fetch("/manualstart",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isstarted:!1})}).then(t=>{t.status==200?alert("CTF stopped"):alert("💀")})}function Lt(t,e,l){let a=[!1,!1,!1,!1],s=[{name:"",password:"",score:0,solved:[]}],r=[],h=[],c=[{name:"",files:h,flag:"",points:0,description:"",country:""}],d="",n="",f,x,_,w,k,S=0;function B(){for(let p=0;pp.json()).then(p=>{l(1,s=p.users)})}function P(){let p=new Date(_+" "+f).getTime()/1e3,N=new Date(w+" "+x).getTime()/1e3;p{te.status==200?alert("Time added"):alert("💀")}):p>N?alert("Start time must be before end time"):p==N?alert("CTF has to run at leats 1 second"):alert("Enter a valid time")}function I(){fetch("/announcement").then(p=>p.json()).then(p=>{S=p.announcements.length}),S==0?S=0:S=S+1,fetch("/announcement",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:k,id:S})}).then(p=>{p.status==200?alert("Announcment posted"):alert("💀")})}function L(){document.getElementById("makeChallangeFile").click(),document.querySelector("#makeChallangeFile").addEventListener("change",N=>{const te=N.target.files[0],ne=new FileReader;ne.onloadend=()=>{const de=ne.result.replace("data:","").replace(/^.+,/,"");h.push({filename:te.name,base64:de}),console.log(c[0])},ne.readAsDataURL(te)}),l(3,h)}function D(){l(4,c),fetch("/upload",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:c[0].name,files:c[0].files,flag:c[0].flag,points:c[0].points,description:c[0].description,country:c[0].country})}).then(p=>{p.status==200?alert("Challange added"):alert("💀")})}function A(){fetch("/register",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:d,password:n})}).then(p=>{p.status==200?alert("Team added"):alert("Team already exists")}).then(p=>{fetch("/teams").then(N=>N.json()).then(N=>{l(1,s=N.users)})})}function T(p){var N=s[p].username;fetch("/teams",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:N})}).then(te=>{te.status==200?alert("Team deleted"):alert("💀")}).then(te=>{fetch("/teams").then(ne=>ne.json()).then(ne=>{l(1,s=ne.users)})})}function v(){_=this.value,l(9,_)}function g(){f=this.value,l(7,f)}function b(){w=this.value,l(10,w)}function E(){x=this.value,l(8,x)}function U(){k=this.value,l(11,k)}function R(){c[0].name=this.value,l(4,c)}function q(){c[0].description=this.value,l(4,c)}function V(){c[0].country=this.value,l(4,c)}function ee(){c[0].flag=this.value,l(4,c)}function C(){c[0].points=$e(this.value),l(4,c)}const Q=()=>T(nt);function Y(){d=this.value,l(5,d)}function fe(){n=this.value,l(6,n)}return[a,s,r,h,c,d,n,f,x,_,w,k,B,z,F,M,P,I,L,D,A,T,v,g,b,E,U,R,q,V,ee,C,Q,Y,fe]}class Nt extends ve{constructor(e){super(),pe(this,e,Lt,Mt,ge,{},null,[-1,-1])}}function Bt(t){let e,l,a;return l=new Nt({}),{c(){e=u("main"),me(l.$$.fragment)},m(s,r){K(s,e,r),ce(l,e,null),a=!0},p:J,i(s){a||(oe(l.$$.fragment,s),a=!0)},o(s){re(l.$$.fragment,s),a=!1},d(s){s&&G(e),ue(l)}}}class Ht extends ve{constructor(e){super(),pe(this,e,null,Bt,ge,{})}}new Ht({target:document.getElementById("app")}); 2 | -------------------------------------------------------------------------------- /frontend/dist_collected/assets/index-bbff9fea.js: -------------------------------------------------------------------------------- 1 | (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))o(r);new MutationObserver(r=>{for(const l of r)if(l.type==="childList")for(const i of l.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&o(i)}).observe(document,{childList:!0,subtree:!0});function n(r){const l={};return r.integrity&&(l.integrity=r.integrity),r.referrerPolicy&&(l.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?l.credentials="include":r.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function o(r){if(r.ep)return;r.ep=!0;const l=n(r);fetch(r.href,l)}})();function $(){}function K(e){return e()}function F(){return Object.create(null)}function E(e){e.forEach(K)}function W(e){return typeof e=="function"}function k(e,t){return e!=e?t==t:e!==t||e&&typeof e=="object"||typeof e=="function"}function X(e){return Object.keys(e).length===0}function c(e,t){e.appendChild(t)}function z(e,t,n){e.insertBefore(t,n||null)}function M(e){e.parentNode&&e.parentNode.removeChild(e)}function d(e){return document.createElement(e)}function Y(e){return document.createTextNode(e)}function w(){return Y(" ")}function S(e,t,n,o){return e.addEventListener(t,n,o),()=>e.removeEventListener(t,n,o)}function u(e,t,n){n==null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function Z(e){return Array.from(e.childNodes)}function C(e,t){e.value=t??""}let q;function O(e){q=e}const y=[],H=[],N=[],J=[],ee=Promise.resolve();let T=!1;function te(){T||(T=!0,ee.then(D))}function I(e){N.push(e)}const j=new Set;let _=0;function D(){if(_!==0)return;const e=q;do{try{for(;_{A.delete(e),o&&(n&&e.d(1),o())}),e.o(t)}else o&&o()}function se(e){e&&e.c()}function Q(e,t,n,o){const{fragment:r,after_update:l}=e.$$;r&&r.m(t,n),o||I(()=>{const i=e.$$.on_mount.map(K).filter(W);e.$$.on_destroy?e.$$.on_destroy.push(...i):E(i),e.$$.on_mount=[]}),l.forEach(I)}function R(e,t){const n=e.$$;n.fragment!==null&&(E(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function ie(e,t){e.$$.dirty[0]===-1&&(y.push(e),te(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const x=f.length?f[0]:v;return s.ctx&&r(s.ctx[a],s.ctx[a]=x)&&(!s.skip_bound&&s.bound[a]&&s.bound[a](x),b&&ie(e,a)),v}):[],s.update(),b=!0,E(s.before_update),s.fragment=o?o(s.ctx):!1,t.target){if(t.hydrate){const a=Z(t.target);s.fragment&&s.fragment.l(a),a.forEach(M)}else s.fragment&&s.fragment.c();t.intro&&G(e.$$.fragment),Q(e,t.target,t.anchor,t.customElement),D()}O(m)}class V{$destroy(){R(this,1),this.$destroy=$}$on(t,n){if(!W(n))return $;const o=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return o.push(n),()=>{const r=o.indexOf(n);r!==-1&&o.splice(r,1)}}$set(t){this.$$set&&!X(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}function le(e){let t,n,o,r,l,i,p,m,s,b,a,v,f,x,g,P,B;return{c(){t=d("main"),n=d("div"),o=d("div"),r=d("h1"),r.textContent="CTF Login",l=w(),i=d("div"),p=d("label"),p.textContent="team_name",m=w(),s=d("input"),b=w(),a=d("label"),a.textContent="password",v=w(),f=d("input"),x=w(),g=d("button"),g.innerHTML='

Login

',u(r,"class","text-gray-300 text-4xl text-center font-bold mb-3 font-mono"),u(p,"class","text-gray-300 font-mono mb-1"),u(p,"for","username"),u(s,"class","rounded-lg h-9 w-full p-2 transition-all bg-black hover:bg-violet-900 text-gray-300"),u(s,"type","text"),u(s,"name","username"),u(s,"id","username"),u(a,"class","text-gray-300 font-mono mt-2 mb-1"),u(a,"for","password"),u(f,"class","rounded-lg h-9 w-full p-2 transition-all bg-black hover:bg-violet-900 text-gray-300"),u(f,"type","password"),u(f,"name","password"),u(f,"id","password"),u(g,"class","bg-gray-900 w-32 h-12 rounded-xl transition-all border-2 hover:border-4 border-violet-500 mt-2"),u(g,"type","submit"),u(i,"class","grid place-items-center"),u(o,"class","w-64"),u(n,"id","body"),u(n,"class","grid w-screen h-screen place-content-center svelte-deiqed")},m(h,L){z(h,t,L),c(t,n),c(n,o),c(o,r),c(o,l),c(o,i),c(i,p),c(i,m),c(i,s),C(s,e[0]),c(i,b),c(i,a),c(i,v),c(i,f),C(f,e[1]),c(i,x),c(i,g),P||(B=[S(s,"input",e[3]),S(f,"input",e[4]),S(g,"click",e[2])],P=!0)},p(h,[L]){L&1&&s.value!==h[0]&&C(s,h[0]),L&2&&f.value!==h[1]&&C(f,h[1])},i:$,o:$,d(h){h&&M(t),P=!1,E(B)}}}function ue(e,t,n){let o,r;function l(){fetch("/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:o,password:r})}).then(m=>{m.status==200?window.location.href="/ctf":alert("Wrong username or password")})}function i(){o=this.value,n(0,o)}function p(){r=this.value,n(1,r)}return[o,r,l,i,p]}class ae extends V{constructor(t){super(),U(this,t,ue,le,k,{})}}function ce(e){let t,n,o;return n=new ae({}),{c(){t=d("main"),se(n.$$.fragment)},m(r,l){z(r,t,l),Q(n,t,null),o=!0},p:$,i(r){o||(G(n.$$.fragment,r),o=!0)},o(r){oe(n.$$.fragment,r),o=!1},d(r){r&&M(t),R(n)}}}class fe extends V{constructor(t){super(),U(this,t,null,ce,k,{})}}new fe({target:document.getElementById("app")}); 2 | -------------------------------------------------------------------------------- /frontend/dist_collected/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Svelte 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Svelte 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/App.svelte: -------------------------------------------------------------------------------- 1 | 6 |
7 | 8 |
-------------------------------------------------------------------------------- /frontend/src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /frontend/src/lib/Admin.svelte: -------------------------------------------------------------------------------- 1 | 271 |
272 |
273 | 276 |
277 |
278 |
279 | 280 |

General

281 |
282 |
283 | 284 |

Announcment

285 |
286 |
287 | 288 |

Challanges

289 |
290 |
291 | 292 |

Teams

293 |
294 |
295 | {#if pages[0] == false && pages[1] == false && pages[2] == false && pages[3] == false} 296 |
Later...
297 | {:else if pages[0] == true} 298 |
299 |

General

300 |
301 |
302 | 303 | 304 | 305 |
306 |
307 | 308 | 309 | 310 |
311 |
312 |
313 | 314 |
315 |

Manual control:

316 |
317 | 318 | 319 |
320 |

Import/Export Challanges:

321 |
322 | 323 | 324 | 325 |
326 |
327 | {:else if pages[1] == true} 328 |
329 |

Announcment

330 |
331 | 332 | 333 |
334 |
335 | 336 |
337 |
338 | {:else if pages[2] == true} 339 |
340 |

Challanges

341 |
342 | {#each challanges as challange} 343 |
344 |

Name:

345 | 346 |
347 |
348 |

Flag:

349 | 350 |
351 |
352 |

Points:

353 | 354 |
355 |
356 |

Files:

357 | {#each challange.files as file} 358 |

{file.name}

359 | {/each} 360 |
361 | 362 |
363 |
364 | {/each} 365 |
366 | 367 |
368 |
369 |
370 |
371 |

Name:

372 | 373 |
374 |
375 |

Description:

376 | 377 |
378 |
379 |

Country:

380 | 381 |
382 |
383 |
384 |
385 |

Flag:

386 | 387 |
388 |
389 |

Points:

390 | 391 |
392 |
393 |

Files:

394 | {#each files as file} 395 |

{file.name}

396 | {/each} 397 |
398 | 399 | 400 | 401 |
402 |
403 |
404 |
405 | {:else if pages[3] == true} 406 |
407 |

Teams

408 |
409 | {#each teams as team} 410 |
411 |

Username:

412 | 413 |

Password:

414 | 415 |
416 | 417 | {teamIndex += 1} 418 |
419 |
420 | {/each} 421 |
422 | 423 |
424 |
425 |
426 | 427 | 428 |
429 |
430 | 431 | 432 |
433 |
434 |
435 | 436 |
437 |
438 |
439 | {/if} 440 |
441 |
442 |
443 | -------------------------------------------------------------------------------- /frontend/src/lib/Index.svelte: -------------------------------------------------------------------------------- 1 | 24 |
25 |
26 |
27 |

CTF Login

28 |
29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import './app.css' 2 | import App from './App.svelte' 3 | 4 | const app = new App({ 5 | target: document.getElementById('app'), 6 | }) 7 | 8 | export default app 9 | -------------------------------------------------------------------------------- /frontend_dev/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /frontend_dev/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /frontend_dev/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /frontend_dev/README.md: -------------------------------------------------------------------------------- 1 | # Astro Starter Kit: Basics 2 | 3 | ``` 4 | npm create astro@latest -- --template basics 5 | ``` 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 8 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics) 9 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json) 10 | 11 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 12 | 13 | ![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png) 14 | 15 | 16 | ## 🚀 Project Structure 17 | 18 | Inside of your Astro project, you'll see the following folders and files: 19 | 20 | ``` 21 | / 22 | ├── public/ 23 | │ └── favicon.svg 24 | ├── src/ 25 | │ ├── components/ 26 | │ │ └── Card.astro 27 | │ ├── layouts/ 28 | │ │ └── Layout.astro 29 | │ └── pages/ 30 | │ └── index.astro 31 | └── package.json 32 | ``` 33 | 34 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 35 | 36 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 37 | 38 | Any static assets, like images, can be placed in the `public/` directory. 39 | 40 | ## 🧞 Commands 41 | 42 | All commands are run from the root of the project, from a terminal: 43 | 44 | | Command | Action | 45 | | :--------------------- | :----------------------------------------------- | 46 | | `npm install` | Installs dependencies | 47 | | `npm run dev` | Starts local dev server at `localhost:3000` | 48 | | `npm run build` | Build your production site to `./dist/` | 49 | | `npm run preview` | Preview your build locally, before deploying | 50 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 51 | | `npm run astro --help` | Get help using the Astro CLI | 52 | 53 | ## 👀 Want to learn more? 54 | 55 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 56 | -------------------------------------------------------------------------------- /frontend_dev/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import react from "@astrojs/react"; 3 | import tailwind from "@astrojs/tailwind"; 4 | import node from "@astrojs/node"; 5 | 6 | import svelte from "@astrojs/svelte"; 7 | 8 | // https://astro.build/config 9 | export default defineConfig({ 10 | integrations: [react(), tailwind(), svelte()], 11 | output: "server", 12 | adapter: node({ 13 | mode: "standalone" 14 | }) 15 | }); -------------------------------------------------------------------------------- /frontend_dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-dev", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/node": "^5.1.1", 14 | "@astrojs/react": "^2.1.1", 15 | "@astrojs/svelte": "^2.1.0", 16 | "@astrojs/tailwind": "^3.1.1", 17 | "@types/react": "^18.0.21", 18 | "@types/react-dom": "^18.0.6", 19 | "astro": "^2.2.0", 20 | "d3": "^7.8.4", 21 | "globe.gl": "^2.27.1", 22 | "react": "^18.0.0", 23 | "react-dom": "^18.0.0", 24 | "react-globe.gl": "^2.23.3", 25 | "svelte": "^3.54.0", 26 | "svelte-remixicon": "^1.0.1", 27 | "tailwindcss": "^3.0.24" 28 | }, 29 | "devDependencies": { 30 | "tailwindcss-bg-patterns": "^0.2.0" 31 | } 32 | } -------------------------------------------------------------------------------- /frontend_dev/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /frontend_dev/src/components/admin.svelte: -------------------------------------------------------------------------------- 1 | 279 |
280 |
281 | 284 |
285 |
286 | 287 |
288 |

General

289 |
290 | 291 |
292 |

Announcment

293 |
294 | 295 |
296 |

Challanges

297 |
298 | 299 |
300 |

Teams

301 |
302 |
303 | {#if pages[0] == false && pages[1] == false && pages[2] == false && pages[3] == false} 304 |
Later...
305 | {:else if pages[0] == true} 306 |
307 |

General

308 |
309 |
310 | 311 | 312 | 313 |
314 |
315 | 316 | 317 | 318 |
319 |
320 |
321 | 322 |
323 |

Manual control:

324 |
325 | 326 | 327 |
328 |

Import/Export Challanges:

329 |
330 | 331 | 332 | 333 |
334 |
335 | {:else if pages[1] == true} 336 |
337 |

Announcment

338 |
339 | 340 | 341 |
342 |
343 | 344 |
345 |
346 | {:else if pages[2] == true} 347 |
348 |

Challanges

349 |
350 | {#each challanges as challange} 351 |
352 |

Name:

353 | 354 |
355 |
356 |

Flag:

357 | 358 |
359 |
360 |

Points:

361 | 362 |
363 |
364 |

Files:

365 | {#each challange.files as file} 366 |

{file.name}

367 | {/each} 368 |
369 | 370 |
371 |
372 | {/each} 373 |
374 |
375 |
376 |
377 |
378 |

Name:

379 | 380 |
381 |
382 |

Description:

383 | 384 |
385 |
386 |

Country:

387 | 388 |
389 |
390 |
391 |
392 |

Flag:

393 | 394 |
395 |
396 |

Points:

397 | 398 |
399 |
400 |

Files:

401 | {#each files as file} 402 |

{file.name}

403 | {/each} 404 |
405 | 406 | 407 | 408 |
409 |
410 |
411 |
412 | {:else if pages[3] == true} 413 |
414 |

Teams

415 |
416 | {#each teams as team} 417 |
418 |

Username:

419 | 420 |

Password:

421 | 422 |
423 | 424 | {teamIndex += 1} 425 |
426 |
427 | {/each} 428 |
429 |
430 |
431 |
432 | 433 | 434 |
435 |
436 | 437 | 438 |
439 |
440 |
441 | 442 |
443 |
444 |
445 | {/if} 446 |
447 |
448 |
449 | -------------------------------------------------------------------------------- /frontend_dev/src/components/ctfstats.svelte: -------------------------------------------------------------------------------- 1 | 75 | 76 |
77 |
78 |
79 |

Announcements:

80 | {#each announcements as announcement} 81 |

admin: {announcement.message}

82 | {/each} 83 |
84 |
85 |

Leaderboard:

86 | {#each teams as team, order} 87 |

{order + 1}#{team.team}{team.points}

88 | {/each} 89 |
90 |
91 |
TIME LEFT
92 |
{days}:{hours}:{minutes}:{seconds}
93 |
94 |

days

95 |

hours

96 |

minutes

97 |

seconds

98 | 99 |
100 |
101 |
102 |
103 | -------------------------------------------------------------------------------- /frontend_dev/src/components/globe.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useEffect, createElement } from 'react'; 3 | import Globe from 'react-globe.gl'; 4 | 5 | let challenges = []; 6 | 7 | function isCountryUsed(country) { 8 | for (let i = 0; i < challenges.length; i++) { 9 | if (country === challenges[i].country.toUpperCase()) { 10 | return true; 11 | } 12 | } 13 | return false; 14 | } 15 | 16 | function fetchChallenges() { 17 | fetch('/api/challenges').then(res => res.json()).then(data => { 18 | if (data.status === 401) { 19 | alert('CTF is not yet started'); 20 | } 21 | else { 22 | challenges = data.challenges; 23 | } 24 | }); 25 | } 26 | 27 | const types = { 28 | // File Extension MIME Type 29 | 'abs': 'audio/x-mpeg', 30 | 'ai': 'application/postscript', 31 | 'aif': 'audio/x-aiff', 32 | 'aifc': 'audio/x-aiff', 33 | 'aiff': 'audio/x-aiff', 34 | 'aim': 'application/x-aim', 35 | 'art': 'image/x-jg', 36 | 'asf': 'video/x-ms-asf', 37 | 'asx': 'video/x-ms-asf', 38 | 'au': 'audio/basic', 39 | 'avi': 'video/x-msvideo', 40 | 'avx': 'video/x-rad-screenplay', 41 | 'bcpio': 'application/x-bcpio', 42 | 'bin': 'application/octet-stream', 43 | 'bmp': 'image/bmp', 44 | 'body': 'text/html', 45 | 'cdf': 'application/x-cdf', 46 | 'cer': 'application/pkix-cert', 47 | 'class': 'application/java', 48 | 'cpio': 'application/x-cpio', 49 | 'csh': 'application/x-csh', 50 | 'css': 'text/css', 51 | 'dib': 'image/bmp', 52 | 'doc': 'application/msword', 53 | 'dtd': 'application/xml-dtd', 54 | 'dv': 'video/x-dv', 55 | 'dvi': 'application/x-dvi', 56 | 'elf': 'application/x-elf', 57 | 'eot': 'application/vnd.ms-fontobject', 58 | 'eps': 'application/postscript', 59 | 'etx': 'text/x-setext', 60 | 'exe': 'application/octet-stream', 61 | 'gif': 'image/gif', 62 | 'gtar': 'application/x-gtar', 63 | 'gz': 'application/x-gzip', 64 | 'hdf': 'application/x-hdf', 65 | 'hqx': 'application/mac-binhex40', 66 | 'htc': 'text/x-component', 67 | 'htm': 'text/html', 68 | 'html': 'text/html', 69 | 'ief': 'image/ief', 70 | 'jad': 'text/vnd.sun.j2me.app-descriptor', 71 | 'jar': 'application/java-archive', 72 | 'java': 'text/x-java-source', 73 | 'jnlp': 'application/x-java-jnlp-file', 74 | 'jpe': 'image/jpeg', 75 | 'jpeg': 'image/jpeg', 76 | 'jpg': 'image/jpeg', 77 | 'js': 'application/javascript', 78 | 'jsf': 'text/plain', 79 | 'json': 'application/json', 80 | 'jspf': 'text/plain', 81 | 'kar': 'audio/midi', 82 | 'latex': 'application/x-latex', 83 | 'm3u': 'audio/x-mpegurl', 84 | 'mac': 'image/x-macpaint', 85 | 'man': 'text/troff', 86 | 'mathml': 'application/mathml+xml', 87 | 'me': 'text/troff', 88 | 'mid': 'audio/midi', 89 | 'midi': 'audio/midi', 90 | 'mif': 'application/x-mif', 91 | 'mov': 'video/quicktime', 92 | 'movie': 'video/x-sgi-movie', 93 | 'mp1': 'audio/mpeg', 94 | 'mp2': 'audio/mpeg', 95 | 'mp3': 'audio/mpeg', 96 | 'mp4': 'video/mp4', 97 | 'mpa': 'audio/mpeg', 98 | 'mpe': 'video/mpeg', 99 | 'mpeg': 'video/mpeg', 100 | 'mpega': 'audio/x-mpeg', 101 | 'mpg': 'video/mpeg', 102 | 'mpv2': 'video/mpeg2', 103 | 'ms': 'application/x-wais-source', 104 | 'nc': 'application/x-netcdf', 105 | 'oda': 'application/oda', 106 | 'odb': 'application/vnd.oasis.opendocument.database', 107 | 'odc': 'application/vnd.oasis.opendocument.chart', 108 | 'odf': 'application/vnd.oasis.opendocument.formula', 109 | 'odg': 'application/vnd.oasis.opendocument.graphics', 110 | 'odi': 'application/vnd.oasis.opendocument.image', 111 | 'odm': 'application/vnd.oasis.opendocument.text-master', 112 | 'odp': 'application/vnd.oasis.opendocument.presentation', 113 | 'ods': 'application/vnd.oasis.opendocument.spreadsheet', 114 | 'odt': 'application/vnd.oasis.opendocument.text', 115 | 'otg': 'application/vnd.oasis.opendocument.graphics-template', 116 | 'oth': 'application/vnd.oasis.opendocument.text-web', 117 | 'otp': 'application/vnd.oasis.opendocument.presentation-template', 118 | 'ots': 'application/vnd.oasis.opendocument.spreadsheet-template', 119 | 'ott': 'application/vnd.oasis.opendocument.text-template', 120 | 'ogx': 'application/ogg', 121 | 'ogv': 'video/ogg', 122 | 'oga': 'audio/ogg', 123 | 'ogg': 'audio/ogg', 124 | 'otf': 'application/x-font-opentype', 125 | 'spx': 'audio/ogg', 126 | 'flac': 'audio/flac', 127 | 'anx': 'application/annodex', 128 | 'axa': 'audio/annodex', 129 | 'axv': 'video/annodex', 130 | 'xspf': 'application/xspf+xml', 131 | 'pcap': 'application/vnd.tcpdump.pcap', 132 | 'pbm': 'image/x-portable-bitmap', 133 | 'pct': 'image/pict', 134 | 'pdf': 'application/pdf', 135 | 'pgm': 'image/x-portable-graymap', 136 | 'pic': 'image/pict', 137 | 'pict': 'image/pict', 138 | 'pls': 'audio/x-scpls', 139 | 'png': 'image/png', 140 | 'pnm': 'image/x-portable-anymap', 141 | 'pnt': 'image/x-macpaint', 142 | 'ppm': 'image/x-portable-pixmap', 143 | 'ppt': 'application/vnd.ms-powerpoint', 144 | 'pps': 'application/vnd.ms-powerpoint', 145 | 'ps': 'application/postscript', 146 | 'psd': 'image/vnd.adobe.photoshop', 147 | 'qt': 'video/quicktime', 148 | 'qti': 'image/x-quicktime', 149 | 'qtif': 'image/x-quicktime', 150 | 'ras': 'image/x-cmu-raster', 151 | 'rdf': 'application/rdf+xml', 152 | 'rgb': 'image/x-rgb', 153 | 'rm': 'application/vnd.rn-realmedia', 154 | 'roff': 'text/troff', 155 | 'rtf': 'application/rtf', 156 | 'rtx': 'text/richtext', 157 | 'sfnt': 'application/font-sfnt', 158 | 'sh': 'application/x-sh', 159 | 'shar': 'application/x-shar', 160 | 'sit': 'application/x-stuffit', 161 | 'snd': 'audio/basic', 162 | 'src': 'application/x-wais-source', 163 | 'sv4cpio': 'application/x-sv4cpio', 164 | 'sv4crc': 'application/x-sv4crc', 165 | 'svg': 'image/svg+xml', 166 | 'svgz': 'image/svg+xml', 167 | 'swf': 'application/x-shockwave-flash', 168 | 't': 'text/troff', 169 | 'tar': 'application/x-tar', 170 | 'tcl': 'application/x-tcl', 171 | 'tex': 'application/x-tex', 172 | 'texi': 'application/x-texinfo', 173 | 'texinfo': 'application/x-texinfo', 174 | 'tif': 'image/tiff', 175 | 'tiff': 'image/tiff', 176 | 'tr': 'text/troff', 177 | 'tsv': 'text/tab-separated-values', 178 | 'ttf': 'application/x-font-ttf', 179 | 'txt': 'text/plain', 180 | 'ulw': 'audio/basic', 181 | 'ustar': 'application/x-ustar', 182 | 'vxml': 'application/voicexml+xml', 183 | 'xbm': 'image/x-xbitmap', 184 | 'xht': 'application/xhtml+xml', 185 | 'xhtml': 'application/xhtml+xml', 186 | 'xls': 'application/vnd.ms-excel', 187 | 'xml': 'application/xml', 188 | 'xpm': 'image/x-xpixmap', 189 | 'xsl': 'application/xml', 190 | 'xslt': 'application/xslt+xml', 191 | 'xul': 'application/vnd.mozilla.xul+xml', 192 | 'xwd': 'image/x-xwindowdump', 193 | 'vsd': 'application/vnd.visio', 194 | 'wav': 'audio/x-wav', 195 | 'wbmp': 'image/vnd.wap.wbmp', 196 | 'wml': 'text/vnd.wap.wml', 197 | 'wmlc': 'application/vnd.wap.wmlc', 198 | 'wmls': 'text/vnd.wap.wmlsc', 199 | 'wmlscriptc': 'application/vnd.wap.wmlscriptc', 200 | 'wmv': 'video/x-ms-wmv', 201 | 'woff': 'application/font-woff', 202 | 'woff2': 'application/font-woff2', 203 | 'wrl': 'model/vrml', 204 | 'wspolicy': 'application/wspolicy+xml', 205 | 'z': 'application/x-compress', 206 | 'zip': 'application/zip' 207 | }; 208 | 209 | export default function InterfaceGlobe() { 210 | const [isHover, setIsHover] = useState(false); 211 | const [geoData, setGeoData] = useState([]); 212 | const [hoverD, setHoverD] = useState(null); 213 | 214 | const handleMouseEnter = () => { 215 | setIsHover(true); 216 | }; 217 | 218 | const handleMouseLeave = () => { 219 | setIsHover(false); 220 | }; 221 | 222 | useEffect(() => { 223 | fetch('./map.geojson').then(res => res.json()).then(data => setGeoData(data.features)); 224 | }, []); 225 | 226 | function flagSubmit() { 227 | let flag = document.getElementById('challengeFlag').value; 228 | let challange = document.getElementById('challengeName').innerHTML; 229 | fetch('/api/validate', { 230 | method: 'POST', 231 | headers: { 232 | 'Content-Type': 'application/json', 233 | }, 234 | body: JSON.stringify({ 235 | challenge: challange, 236 | value: flag 237 | }) 238 | }) 239 | .then(response => response.status) 240 | .then(status => { 241 | if (status === 200) { 242 | alert("Correct Flag!"); 243 | } else if (status === 403) { 244 | alert("Already anwsered!"); 245 | } else if (status === 409) { 246 | alert("Wrong Flag!"); 247 | } else { 248 | alert("Something went wrong!"); 249 | } 250 | 251 | }) 252 | } 253 | 254 | function download() { 255 | let files = [] 256 | for (let i = 0; i < challenges.length; i++) { 257 | if (challenges[i].name === document.getElementById('challengeName').innerHTML) { 258 | for (let j = 0; j < challenges[i].files.length; j++) { 259 | files.push({ filename : challenges[i].files[j].filename, base64 : challenges[i].files[j].base64 }); 260 | } 261 | } 262 | } 263 | 264 | if (files == []) { 265 | return 266 | } 267 | 268 | for (let i = 0; i < files.length; i++) { 269 | var downloadLink = document.createElement("a"); 270 | var type = ""; 271 | for (let j = 0; j < types.length; j++) { 272 | if (files[i].filename.includes(types[i].extension)) { 273 | type = types[i].type; 274 | } 275 | } 276 | downloadLink.href = "data:" + type + ";base64," + files[i].base64; 277 | downloadLink.download = files[i].filename; 278 | downloadLink.click(); 279 | } 280 | } 281 | 282 | const handlePolygonHover = (polygon) => { 283 | setHoverD(polygon); 284 | } 285 | 286 | function getChallengeByCountry(country) { 287 | for (let i = 0; i < challenges.length; i++) { 288 | if (country === challenges[i].country.toUpperCase()) { 289 | return challenges[i]; 290 | } 291 | } 292 | } 293 | 294 | function handlePolygonClick(d) { 295 | if (isCountryUsed(d.properties.ISO_A2) === true) { 296 | let challenge = getChallengeByCountry(d.properties.ISO_A2); 297 | document.getElementById('challengeBlock').style.display = 'block'; 298 | document.getElementById('challengeName').innerHTML = challenge.name; 299 | document.getElementById('challengeCountry').innerHTML = d.properties.ADMIN; 300 | document.getElementById('challengePoints').innerHTML = challenge.points + ' points'; 301 | document.getElementById('challengeDescription').innerHTML = challenge.description; 302 | addEventListener('click', () => { 303 | if(event.target.id !== 'challengeBlock' && event.target.className !== 'challengeInfo') { 304 | document.getElementById('challengeBlock').style.display = 'none'; 305 | } 306 | }); 307 | } 308 | else { 309 | document.getElementById('challengeBlock').style.display = 'none'; 310 | } 311 | } 312 | 313 | return ( 314 |
315 | d.properties.ISO_A2 !== 'AQ')} 319 | polygonCapColor={d => isCountryUsed(d.properties.ISO_A2) ? '#3d2473' : 'rgb(22, 22, 22)'} 320 | polygonSideColor = {() => 'rgb(93, 52, 179)'} 321 | polygonStrokeColor = {() => '#5D34B3'} 322 | onPolygonHover={handlePolygonHover} 323 | polygonAltitude={d => (hoverD && d.properties.ADMIN === hoverD.properties.ADMIN ? 0.04 : 0.02)} 324 | polygonsTransitionDuration={150} 325 | onPolygonClick={d => handlePolygonClick(d)} 326 | /> 327 | 328 |
329 |
330 |
331 |

Challenge

332 |

333 |

334 |
335 |
336 |

points: 0

337 | 338 | 339 | 340 |
341 |
342 |
343 |
344 | ); 345 | } 346 | 347 | fetchChallenges(); 348 | setInterval(fetchChallenges, 2*60*1000) 349 | -------------------------------------------------------------------------------- /frontend_dev/src/components/login.svelte: -------------------------------------------------------------------------------- 1 | 33 |
34 |
35 |
36 |

CTF Login

37 |
38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 |
46 |
47 |
48 | 56 | -------------------------------------------------------------------------------- /frontend_dev/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend_dev/src/pages/ctf.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Stats from '../components/ctfstats.svelte'; 3 | import InterfaceGlobe from '../components/globe.jsx'; 4 | 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend_dev/src/pages/dashboard.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Admin from "../components/admin.svelte"; 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend_dev/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Login from '../components/login.svelte'; 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend_dev/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@astrojs/svelte'; 2 | 3 | export default { 4 | preprocess: vitePreprocess(), 5 | }; 6 | -------------------------------------------------------------------------------- /frontend_dev/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], 4 | theme: { 5 | extend: { 6 | colors: { 7 | black1: "#000", 8 | }, 9 | }, 10 | patterns: { 11 | colors: { 12 | black1: "#000", 13 | }, 14 | opacities: { 15 | 100: "1", 16 | 80: ".80", 17 | 60: ".60", 18 | 40: ".40", 19 | 20: ".20", 20 | 10: ".10", 21 | 5: ".05", 22 | }, 23 | sizes: { 24 | 1: "0.25rem", 25 | 2: "0.5rem", 26 | 4: "1rem", 27 | 6: "1.5rem", 28 | 8: "2rem", 29 | 16: "4rem", 30 | 20: "5rem", 31 | 24: "6rem", 32 | 32: "8rem" 33 | }, 34 | }, 35 | }, 36 | plugins: [ 37 | require('tailwindcss-bg-patterns'), 38 | ], 39 | } 40 | -------------------------------------------------------------------------------- /frontend_dev/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "jsxImportSource": "react" 6 | } 7 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Fabucik/ctf-portal 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/bytedance/sonic v1.8.3 // indirect 7 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 8 | github.com/gin-contrib/gzip v0.0.6 // indirect 9 | github.com/gin-contrib/sse v0.1.0 // indirect 10 | github.com/gin-gonic/gin v1.9.0 // indirect 11 | github.com/go-playground/locales v0.14.1 // indirect 12 | github.com/go-playground/universal-translator v0.18.1 // indirect 13 | github.com/go-playground/validator/v10 v10.11.2 // indirect 14 | github.com/goccy/go-json v0.10.0 // indirect 15 | github.com/google/uuid v1.3.0 // indirect 16 | github.com/jackc/pgpassfile v1.0.0 // indirect 17 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 18 | github.com/jackc/pgx/v5 v5.3.1 // indirect 19 | github.com/jinzhu/inflection v1.0.0 // indirect 20 | github.com/jinzhu/now v1.1.5 // indirect 21 | github.com/json-iterator/go v1.1.12 // indirect 22 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 23 | github.com/leodido/go-urn v1.2.2 // indirect 24 | github.com/mattn/go-isatty v0.0.17 // indirect 25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 26 | github.com/modern-go/reflect2 v1.0.2 // indirect 27 | github.com/pelletier/go-toml/v2 v2.0.7 // indirect 28 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 29 | github.com/ugorji/go/codec v1.2.10 // indirect 30 | golang.org/x/arch v0.2.0 // indirect 31 | golang.org/x/crypto v0.9.0 // indirect 32 | golang.org/x/net v0.10.0 // indirect 33 | golang.org/x/sys v0.8.0 // indirect 34 | golang.org/x/text v0.9.0 // indirect 35 | google.golang.org/protobuf v1.28.1 // indirect 36 | gopkg.in/yaml.v3 v3.0.1 // indirect 37 | gorm.io/driver/postgres v1.5.0 // indirect 38 | gorm.io/gorm v1.25.1 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.8.3 h1:pf6fGl5eqWYKkx1RcD4qpuX+BIUaduv/wTm5ekWJ80M= 3 | github.com/bytedance/sonic v1.8.3/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= 11 | github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= 12 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 13 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 14 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= 15 | github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= 16 | github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= 17 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 18 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 19 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 20 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 21 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 22 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 23 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 24 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= 25 | github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= 26 | github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= 27 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 28 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= 29 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 30 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 31 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 32 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 33 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 34 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 35 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 36 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 37 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= 38 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 39 | github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= 40 | github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= 41 | github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= 42 | github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 43 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 44 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 45 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 46 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 47 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 48 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 49 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 50 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 51 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 52 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 53 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 54 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 55 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 56 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 57 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 58 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 59 | github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= 60 | github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= 61 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 62 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 63 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 64 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 65 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 66 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 67 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 68 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 69 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= 70 | github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= 71 | github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 72 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 73 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 74 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 75 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 76 | github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= 77 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 78 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 79 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 80 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 81 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 82 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 83 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 84 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 85 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 86 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 87 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 88 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 89 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 90 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 91 | github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= 92 | github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 93 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 94 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 95 | golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY= 96 | golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 97 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 98 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 99 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 100 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 101 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 102 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 103 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 104 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 105 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 106 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 107 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 108 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 109 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 110 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 111 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 112 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 113 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 114 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 115 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 116 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 117 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 118 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 119 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 120 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 121 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 122 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 123 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 124 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 125 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 126 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 127 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 128 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 129 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 130 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 131 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 132 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 133 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 134 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 135 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 136 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 137 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 138 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 139 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 140 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 141 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 142 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 143 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 144 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 145 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 146 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 147 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 148 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 149 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 150 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 151 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 152 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 153 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 154 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 155 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 156 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 157 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 158 | gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= 159 | gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= 160 | gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 161 | gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= 162 | gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 163 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 164 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Fabucik/ctf-portal/authentication" 5 | "github.com/Fabucik/ctf-portal/ctfsrc" 6 | "github.com/gin-gonic/gin" 7 | "github.com/gin-contrib/gzip" 8 | ) 9 | 10 | func main() { 11 | gin.SetMode(gin.ReleaseMode) 12 | server := gin.Default() 13 | server.Use(gzip.Gzip(gzip.DefaultCompression)) 14 | 15 | server.LoadHTMLFiles("frontend/dist_collected/index.html", "frontend/dist_collected/admin.html", "frontend/dist_collected/CTF.html") 16 | server.Static("/assets", "./frontend/dist_collected/assets") 17 | 18 | //authentication stuff 19 | server.POST("/api/register", authentication.Register) 20 | server.POST("/api/login", authentication.Login) 21 | 22 | // challenge upload 23 | server.POST("/api/upload", ctfsrc.CreateChallenge) 24 | 25 | // flag validation 26 | server.POST("/api/validate", ctfsrc.ValidateFlag) 27 | 28 | // lists challenges 29 | server.GET("/api/challenges", ctfsrc.GetChallenges) 30 | 31 | // team management 32 | server.GET("/api/teams", ctfsrc.GetTeams) 33 | server.DELETE("/api/teams", ctfsrc.DeleteTeam) 34 | 35 | // returns json with all teams and their points 36 | server.GET("/api/points", ctfsrc.GetAllPoints) 37 | 38 | // ctf start and stop 39 | server.POST("/api/timedstart", ctfsrc.SetTime) 40 | server.GET("/api/timedstart", ctfsrc.GetTime) 41 | server.POST("/api/manualstart", ctfsrc.SetManualStartStop) 42 | server.GET("/api/manualstart", ctfsrc.GetManualStartStop) 43 | 44 | // announcement stuff 45 | server.POST("/api/announcement", ctfsrc.CreateAnnouncement) 46 | server.GET("/api/announcement", ctfsrc.GetAnnouncements) 47 | server.DELETE("/api/announcement", ctfsrc.DeleteAnnouncement) 48 | 49 | // importing and exporting CTF challenges 50 | server.GET("/api/backup", ctfsrc.Export) 51 | server.POST("/api/backup", ctfsrc.Import) 52 | 53 | server.Run(":8888") 54 | } 55 | -------------------------------------------------------------------------------- /servehtml/admin.go: -------------------------------------------------------------------------------- 1 | package servehtml 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/Fabucik/ctf-portal/authentication" 8 | "github.com/Fabucik/ctf-portal/entities" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func AdminHTML(ctx *gin.Context) { 13 | var session entities.Session 14 | cookie, _ := ctx.Cookie("session") 15 | json.Unmarshal([]byte(cookie), &session) 16 | 17 | if !authentication.IsAdmin(ctx, session) { 18 | return 19 | } 20 | 21 | ctx.HTML(http.StatusOK, "admin.html", gin.H{}) 22 | } 23 | -------------------------------------------------------------------------------- /servehtml/ctf.go: -------------------------------------------------------------------------------- 1 | package servehtml 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/Fabucik/ctf-portal/authentication" 8 | "github.com/Fabucik/ctf-portal/entities" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func CtfHTML(ctx *gin.Context) { 13 | var session entities.Session 14 | cookie, _ := ctx.Cookie("session") 15 | json.Unmarshal([]byte(cookie), &session) 16 | 17 | if !authentication.IsValidSession(session) { 18 | ctx.JSON(http.StatusUnauthorized, gin.H{ 19 | "message": "Not logged in", 20 | }) 21 | 22 | return 23 | } 24 | 25 | ctx.HTML(http.StatusOK, "CTF.html", gin.H{}) 26 | } 27 | -------------------------------------------------------------------------------- /servehtml/login.go: -------------------------------------------------------------------------------- 1 | package servehtml 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func LoginHTML(ctx *gin.Context) { 10 | ctx.HTML(http.StatusOK, "index.html", gin.H{}) 11 | } 12 | -------------------------------------------------------------------------------- /startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # PATH TO THIS REPO 4 | cd $USER/home/ctf-portal 5 | 6 | bin/ctf-portal --------------------------------------------------------------------------------