diff --git a/README.md b/README.md index 2652583..d86cfa6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,15 @@ # CS:GO Hub backend -Use with [csgo-hub-discord](https://github.com/jesperbakhandskemager/csgo-hub-discord) + +## About +This is the backend, which works with Steam's OpenID to gather their Steam ID and convert it to the corresponding CS:GO friend code. +It functions with the [CS:GO Hub Discord bot](https://github.com/jesperbakhandskemager/csgo-hub-discord). + +A user can request a token from the bot by issuing the `/link-steam` command in any server where the bot is present or in the bot's DM's. +The bot takes note of the Discord Id, and sends it along in the token request (the endpoint can only be accessed from localhost for security). + +The bot responds to the user with a formatted link to the `steam.csgohub.xyz` site appended by their token, and the backend gathers various informations from both the database along with data from Discord's API. + +Once a user is linked, anyone can issue the `/show-team` command in any channel the server owner permits and the bot will reply with a list of friend codes for any linked users in the same voice channel. ## Database You need the following tables in MySQL @@ -8,7 +18,7 @@ You need the following tables in MySQL CREATE TABLE `tokens` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `discord_id` varchar(18) COLLATE utf8mb4_unicode_ci NOT NULL, + `discord_id` varchar(21) COLLATE utf8mb4_unicode_ci NOT NULL, `token` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB AUTO_INCREMENT = 17 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci @@ -16,7 +26,7 @@ CREATE TABLE `users` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `created_at` timestamp NOT NULL DEFAULT current_timestamp(), - `discord_id` varchar(18) COLLATE utf8mb4_unicode_ci NOT NULL, + `discord_id` varchar(21) COLLATE utf8mb4_unicode_ci NOT NULL, `friend_code` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT 'NULL', PRIMARY KEY (`id`) ) ENGINE = InnoDB AUTO_INCREMENT = 6 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci diff --git a/assets/checkmark.css b/assets/checkmark.css new file mode 100644 index 0000000..8503ada --- /dev/null +++ b/assets/checkmark.css @@ -0,0 +1,62 @@ + svg { + width: 10rem; + height: 10rem; + display: block; + margin: 40px auto 0; + } + .path { + stroke-dasharray: 1000; + stroke-dashoffset: 0; + } + .path.circle { + -webkit-animation: dash 0.9s ease-in-out; + animation: dash 0.9s ease-in-out; + } + .path.line { + stroke-dashoffset: 1000; + -webkit-animation: dash 0.9s 0.35s ease-in-out forwards; + animation: dash 0.9s 0.35s ease-in-out forwards; + } + .path.check { + stroke-dashoffset: -100; + -webkit-animation: dash-check 0.9s 0.35s ease-in-out forwards; + animation: dash-check 0.9s 0.35s ease-in-out forwards; + } + p.success { + color: #73af55; + } + p.error { + color: #d06079; + } + @-webkit-keyframes dash { + 0% { + stroke-dashoffset: 1000; + } + 100% { + stroke-dashoffset: 0; + } + } + @keyframes dash { + 0% { + stroke-dashoffset: 1000; + } + 100% { + stroke-dashoffset: 0; + } + } + @-webkit-keyframes dash-check { + 0% { + stroke-dashoffset: -100; + } + 100% { + stroke-dashoffset: 900; + } + } + @keyframes dash-check { + 0% { + stroke-dashoffset: -100; + } + 100% { + stroke-dashoffset: 900; + } + } \ No newline at end of file diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..c729174 --- /dev/null +++ b/assets/style.css @@ -0,0 +1,113 @@ +.steam-login, +body { + -webkit-text-size-adjust: 100% +} + +.navbar a, +.steam-login { + text-decoration: inherit +} + +.footer, +.login, +.steam-login, +li a { + text-align: center +} + +.name { + text-align: center; + text-decoration: inherit; + font-weight: 500; +} + +.name:hover { + color: #535bf2; +} + +body { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, .87); + background-color: #242424; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale +} + +h1 { + font-size: 3.2em; + line-height: 1.1 +} + +img { + scale: 1.2 +} + +ul { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden +} + +li .name { + display: block; + color: #fff; + padding: 14px 16px; + text-decoration: none +} + +.navbar { + margin-top: 1.5rem +} + +.navbar img { + border-radius: 50%; + scale: 1.0; + margin-right: 1.5rem +} + +.steam-login { + vertical-align: 5rem; + 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"; + box-sizing: border-box; + border: 1px solid #e5e7eb; + display: inline-block; + align-items: center; + border-radius: .375rem; + background-color: #1b2838; + color: #fff; + padding: .75rem 1.25rem; + font-size: 1.25rem; + line-height: 1.75rem; + font-weight: 700; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 75ms +} + +.steam-login:hover { + background-color: #4b5563 +} + +.steam-login svg { + vertical-align: -.3em; + margin-right: .3rem; + -webkit-filter: invert(100%); + filter: invert(100%) +} + +.footer { + position: fixed; + left: 0; + bottom: 0; + width: 100%; + background-color: #181a1b; + color: #cecece +} \ No newline at end of file diff --git a/csgo-hub-backend b/csgo-hub-backend deleted file mode 100755 index ce803fe..0000000 Binary files a/csgo-hub-backend and /dev/null differ diff --git a/index.html b/index.html deleted file mode 100644 index 4e7f04c..0000000 --- a/index.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - Steam OpenID - - - - - {{if .DiscordName}} - -
-

Authenticate Steam

-

Link {{.DiscordName}} with your Steam account

-
- - Sign in through Steam - -
- {{else if .user}} -

{{.user}} Linked to your Discord account

- {{else}} -

An error occured

- {{end}} -
- - \ No newline at end of file diff --git a/main.go b/main.go index 2517df1..b9d30ac 100644 --- a/main.go +++ b/main.go @@ -147,7 +147,7 @@ func CreateToken(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) discord := vars["discord"] - if len(discord) != 18 { + if len(discord) > 21 { fmt.Fprintf(w, "Bad request") return } @@ -218,9 +218,8 @@ func main() { router.HandleFunc("/discover", discoverHandler) router.HandleFunc("/openidcallback", callbackHandler) router.HandleFunc("/{token}", indexHandler) - // router.HandleFunc("/api/v1/user/{id}", ReturnSingleUser).Methods("GET") + router.PathPrefix("/static/").Handler(http.StripPrefix("/static", http.FileServer(http.Dir("./assets")))) router.HandleFunc("/api/v1/users", GetMultipleUsers) - //router.HandleFunc("/api/v1/user", CreateUser).Methods("POST") router.HandleFunc("/api/v1/token/{discord}", CreateToken) http.ListenAndServe(port, router) } diff --git a/sites/authenticate.html b/sites/authenticate.html new file mode 100644 index 0000000..67f82df --- /dev/null +++ b/sites/authenticate.html @@ -0,0 +1,35 @@ + + + + + + + + CS:GO Hub + + + +
+

Authenticate Steam

+

Link {{.DiscordName}} with your Steam account

+
+ +
+
+ + + \ No newline at end of file diff --git a/sites/success.html b/sites/success.html new file mode 100644 index 0000000..37a9884 --- /dev/null +++ b/sites/success.html @@ -0,0 +1,30 @@ + + + + + + + + + CS:GO Hub + + + +
+

{{.Status}}

+
+ + + + +
+ + + \ No newline at end of file diff --git a/steam.go b/steam.go index 83fb559..b392a4e 100644 --- a/steam.go +++ b/steam.go @@ -16,9 +16,8 @@ import ( "github.com/yohcop/openid-go" ) -// Load the templates once -var templateDir = "./" -var indexTemplate = template.Must(template.ParseFiles(templateDir + "index.html")) +var authenticateTmpl = template.Must(template.ParseFiles("./sites/authenticate.html")) +var successTmpl = template.Must(template.ParseFiles("./sites/success.html")) // NoOpDiscoveryCache implements the DiscoveryCache interface and doesn't cache anything. // For a simple website, I'm not sure you need a cache. @@ -39,6 +38,11 @@ type IndexStruct struct { DiscordName string `json:"DiscordName"` DiscordAvatar string `json:"DiscordAvatar"` } +type SuccessStruct struct { + DiscordName string `json:"DiscordName"` + DiscordAvatar string `json:"DiscordAvatar"` + Status string `json:"Status"` +} type DiscordUser struct { Id string `json:"id"` @@ -93,7 +97,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { expiration := time.Now().Add(time.Hour) cookie := http.Cookie{Name: "token", Value: token, Expires: expiration} http.SetCookie(w, &cookie) - indexTemplate.Execute(w, tmpl) + authenticateTmpl.Execute(w, tmpl) } // discoverHandler calls the Steam openid API and redirects to steam for login. @@ -118,6 +122,7 @@ func ClearToken(token string) error { // callbackHandler handles the response back from Steam. It verifies the callback and then renders // the index template with the logged in user's id. func callbackHandler(w http.ResponseWriter, r *http.Request) { + var tmpl SuccessStruct fullURL := "http://" + domain + r.URL.String() id, err := openid.Verify(fullURL, discoveryCache, nonceStore) @@ -150,6 +155,31 @@ func callbackHandler(w http.ResponseWriter, r *http.Request) { } ClearToken(token) + req, _ := http.NewRequest("GET", "https://discord.com/api/v9/users/"+discordId, nil) + req.Header.Add("Authorization", bearer) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println("Error on response.\n[ERROR] -", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Println("Error while reading the response bytes:", err) + } + var discord DiscordUser + json.Unmarshal(body, &discord) + var discordAvatarURL string + if discord.Avatar == "" { + discordAvatarURL = "https://csgohub.xyz/assets/empty-avatar.png" + } else { + discordAvatarURL = "https://cdn.discordapp.com/avatars/" + discord.Id + "/" + discord.Avatar + ".png?size=100" + } + tmpl.DiscordName = discord.Username + tmpl.DiscordAvatar = discordAvatarURL + var checkId, checkFriend string query = `SELECT discord_id, friend_code FROM users where discord_id = ?` err = db.QueryRow(query, discordId).Scan(&checkId, &checkFriend) @@ -161,8 +191,10 @@ func callbackHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Bad request") } log.Print(err) - w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, "Account link updated") + // w.WriteHeader(http.StatusCreated) + // fmt.Fprintf(w, "Account link updated") + tmpl.Status = "Account link updated" + successTmpl.Execute(w, tmpl) return } @@ -171,10 +203,12 @@ func callbackHandler(w http.ResponseWriter, r *http.Request) { log.Println(err) w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "Bad request") + return } + tmpl.Status = "Account linked" w.WriteHeader(http.StatusCreated) data["user"] = id - indexTemplate.Execute(w, data) + successTmpl.Execute(w, tmpl) } }