package main import ( "encoding/json" "fmt" "io" "log" "net/http" "os" "example.com/lib/dbcommands" "github.com/gin-gonic/gin" "github.com/pelletier/go-toml/v2" "github.com/ravener/discord-oauth2" "github.com/xyproto/randomstring" "golang.org/x/oauth2" "gorm.io/gorm" ) type appConfig struct { API struct { Domain string Port string Https bool } OAuth struct { ClientID string ClientSecret string } } type discordUser struct { Id string Username string Avatar string Discriminator string Public_Flags float64 Flags float64 Banner string Accent_Color float64 Global_Name string Avatar_Decoration_Data struct { Asset string Sku_Id string Expires_At string } Collectibles string Banner_Color string Clan string Primary_Guild string Mfa_Eneabled bool Locale string Premium_Type float64 } var db *gorm.DB var config appConfig var oauthConfig *oauth2.Config func parseConfig(configPath string) appConfig { configFile, err := os.Open(configPath) if err != nil { log.Fatal(err) } configFileContent, err := io.ReadAll(configFile) if err != nil { log.Fatal(err) } var configObject appConfig err = toml.Unmarshal(configFileContent, &configObject) if err != nil { log.Fatal(err) } return configObject } func createDiscordOAuthConfig() *oauth2.Config { protocolString := "" if config.API.Https { protocolString = "https://" config.API.Port = "" } else { protocolString = "http://" config.API.Port = fmt.Sprintf(":%s", config.API.Port) } redirectUrlString := fmt.Sprintf("%s%s%s/auth/callback", protocolString, config.API.Domain, config.API.Port) config := &oauth2.Config{ ClientID: config.OAuth.ClientID, ClientSecret: config.OAuth.ClientSecret, RedirectURL: redirectUrlString, Scopes: []string{discord.ScopeIdentify}, Endpoint: discord.Endpoint, } return config } func loginRedirect(context *gin.Context) { context.Redirect(302, oauthConfig.AuthCodeURL(context.GetString("state"))) } func logoutRedirect(context *gin.Context) { oauthTokenJSON, err := context.Cookie("discord-oauthtoken") if err == nil { dbcommands.LogoutDatabaseUser(db, oauthTokenJSON) context.SetCookie("discord-oauthtoken", "", -1, "", config.API.Domain, false, true) } else { log.Println(err) } context.Redirect(http.StatusTemporaryRedirect, "http://localhost:15995/") } func authCallback(context *gin.Context) { oauthState := randomstring.CookieFriendlyString(32) context.Set("state", oauthState) var err error oauthToken, err := oauthConfig.Exchange(context.Request.Context(), context.Query("code")) if err != nil { context.Redirect(http.StatusInternalServerError, "http://localhost:15995/") } oauthTokenJSON, _ := json.Marshal(oauthToken) context.SetCookie("discord-oauthtoken", string(oauthTokenJSON), 0, "", config.API.Domain, false, false) user := getDiscordUser(context, oauthToken) createOrUpdateUser(context, oauthToken, user) context.Redirect(http.StatusTemporaryRedirect, "http://localhost:15995/dashboard") } func getDiscordUser(context *gin.Context, oauthToken *oauth2.Token) discordUser { response, err := oauthConfig.Client(context.Request.Context(), oauthToken).Get("https://discord.com/api/users/@me") if err != nil || response.StatusCode != 200 { responseMessage := "" if err != nil { responseMessage = err.Error() } else { responseMessage = response.Status } context.JSON(http.StatusInternalServerError, gin.H{ "message": responseMessage, }) } defer response.Body.Close() body, err := io.ReadAll(response.Body) if err != nil { context.JSON(http.StatusInternalServerError, gin.H{ "message": err.Error(), }) } var user discordUser json.Unmarshal(body, &user) return user } func createOrUpdateUser(context *gin.Context, oauthToken *oauth2.Token, user discordUser) { oauthTokenJSON, err := json.Marshal(oauthToken) if err != nil { log.Println(err) } dbUser := dbcommands.User{ DisplayName: user.Global_Name, Username: user.Username, Id: user.Id, Avatar: user.Avatar, AvatarDecoration: user.Avatar_Decoration_Data.Asset, LoginToken: string(oauthTokenJSON), LoggedIn: true, } if dbcommands.DatabaseUserExists(db, user.Id) { dbOAuthToken := dbcommands.GetDatabaseUserToken(db, user.Id) if dbOAuthToken == "" { context.SetCookie("discord-oauthtoken", string(oauthTokenJSON), 0, "", config.API.Domain, false, false) err := dbcommands.UpdateDatabaseUser(db, dbUser) if err != nil { log.Println(err) } } else { context.SetCookie("discord-oauthtoken", dbOAuthToken, 0, "", config.API.Domain, false, false) } } else { err := dbcommands.CreateDatabaseUser(db, dbUser) if err != nil { log.Println(err) } } } func getDashboardInfo(context *gin.Context) { oauthTokenJSON, err := context.Cookie("discord-oauthtoken") if err == nil { var oauthToken *oauth2.Token err := json.Unmarshal([]byte(oauthTokenJSON), &oauthToken) if err == nil { if oauthToken.Valid() { user := getDiscordUser(context, oauthToken) if dbcommands.DatabaseUserLoggedIn(db, user.Id) { context.JSON(http.StatusOK, user) return } else { context.Redirect(http.StatusTemporaryRedirect, "http://localhost:31337/logout") } return } } else { log.Println(err) } } else { log.Println(err) } context.Redirect(http.StatusTemporaryRedirect, "http://localhost:15995/") } func isUserAuthorized(context *gin.Context) { oauthTokenJSON, err := context.Cookie("discord-oauthtoken") if err == nil { var oauthToken *oauth2.Token err := json.Unmarshal([]byte(oauthTokenJSON), &oauthToken) if err == nil { if oauthToken.Valid() { user := getDiscordUser(context, oauthToken) if dbcommands.DatabaseUserLoggedIn(db, user.Id) { context.JSON(http.StatusOK, gin.H{ "message": true, }) } else { context.JSON(http.StatusUnauthorized, gin.H{ "message": true, }) } return } else { log.Println(err) } } else { log.Println(err) } } else { log.Println(err) } context.JSON(http.StatusUnauthorized, gin.H{ "message": false, }) } func main() { db = dbcommands.InitializeDatabase() config = parseConfig("config.toml") oauthConfig = createDiscordOAuthConfig() app := gin.Default() app.Static("/public", "./public") app.GET("/login", loginRedirect) app.GET("/auth/callback", authCallback) app.GET("/logout", logoutRedirect) app.GET("/dashboard", getDashboardInfo) app.GET("/authorized", isUserAuthorized) app.Run(":31337") }