package api import ( "encoding/json" "io" "log" "net/http" authdiscord "example.com/auth/discord" configserver "example.com/config/server" databasecommands "example.com/database/commands" databasemodels "example.com/database/models" "github.com/gin-gonic/gin" "github.com/xyproto/randomstring" "golang.org/x/oauth2" "gorm.io/gorm" ) var GlobalDatabase *gorm.DB var GlobalConfig configserver.AppConfig var GlobalOAuth *oauth2.Config // Private Functions func getDiscordUser(context *gin.Context, oauthToken *oauth2.Token) authdiscord.DiscordUser { response, err := GlobalOAuth.Client(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 } log.Println(responseMessage) log.Println("Assuming the Discord OAuth Key has expired.") context.Redirect(http.StatusUnauthorized, "https://localhost:15995/logout") } defer response.Body.Close() body, err := io.ReadAll(response.Body) if err != nil { log.Println(err) } var user authdiscord.DiscordUser json.Unmarshal(body, &user) return user } // Authentication Workflow func AuthCallback(context *gin.Context) { oauthState := randomstring.CookieFriendlyString(32) context.Set("state", oauthState) var err error oauthToken, err := GlobalOAuth.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, "", GlobalConfig.API.Domain, false, false) context.Set("discord-oauthtoken", string(oauthTokenJSON)) CreateOrUpdateUser(context) context.Redirect(http.StatusTemporaryRedirect, "http://localhost:15995/dashboard") } func AuthLoginRedirect(context *gin.Context) { context.Redirect(302, GlobalOAuth.AuthCodeURL(context.GetString("state"))) } func AuthLogoutRedirect(context *gin.Context) { oauthTokenJSON, err := context.Cookie("discord-oauthtoken") if err == nil { databasecommands.LogoutDatabaseUser(GlobalDatabase, oauthTokenJSON) context.SetCookie("discord-oauthtoken", "", -1, "", GlobalConfig.API.Domain, false, true) } else { log.Println(err) } context.Redirect(http.StatusTemporaryRedirect, "http://localhost:15995/") } // Create & Update Endpoints (post/, put/, patch) func CreateOrUpdateUser(context *gin.Context) { oauthTokenJSON := context.GetString("discord-oauthtoken") err := error(nil) if oauthTokenJSON == "" { oauthTokenJSON, err = context.Cookie("discord-oauthtoken") if err != nil { log.Printf("This really shouldn't happen. %s", err) context.AbortWithStatus(http.StatusInternalServerError) } } var oauthToken *oauth2.Token err = json.Unmarshal([]byte(oauthTokenJSON), &oauthToken) if err != nil { log.Println(err) context.AbortWithStatus(http.StatusBadRequest) } currentDiscordUser := getDiscordUser(context, oauthToken) updatedDatabaseUser := databasemodels.User{ DisplayName: currentDiscordUser.Global_Name, Username: currentDiscordUser.Username, Id: currentDiscordUser.Id, Avatar: currentDiscordUser.Avatar, AvatarDecoration: currentDiscordUser.Avatar_Decoration_Data.Asset, LoginToken: string(oauthTokenJSON), LoggedIn: true, ApiKey: nil, } if databasecommands.GetDatabaseUserExists(GlobalDatabase, currentDiscordUser.Id) { dbOAuthToken := databasecommands.GetDatabaseUserToken(GlobalDatabase, currentDiscordUser.Id) if databasecommands.GetDatabaseUserLoggedInFromDiscordId(GlobalDatabase, currentDiscordUser.Id) { context.SetCookie("discord-oauthtoken", dbOAuthToken, 0, "", GlobalConfig.API.Domain, false, false) updatedDatabaseUser.LoginToken = string(dbOAuthToken) } else { context.SetCookie("discord-oauthtoken", string(oauthTokenJSON), 0, "", GlobalConfig.API.Domain, false, false) } err := databasecommands.UpdateDatabaseUser(GlobalDatabase, updatedDatabaseUser) if err != nil { log.Println(err) context.AbortWithStatus(http.StatusInternalServerError) } } else { err := databasecommands.CreateDatabaseUser(GlobalDatabase, updatedDatabaseUser) if err != nil { log.Println(err) context.Copy().AbortWithStatus(http.StatusInternalServerError) } } } func CreateDatabaseGroup(context *gin.Context) { GetIsUserAuthorized(context) isAuthorized := context.GetBool("is-authorized") if isAuthorized { name := context.Query("name") if name != "" { databasecommands.CreateDatabaseGroup(GlobalDatabase, databasemodels.Group{ Name: name, }) context.Status(http.StatusOK) } else { context.AbortWithStatus(http.StatusBadRequest) } } else { context.AbortWithStatus(http.StatusUnauthorized) } } func CreateDatabaseFunction(context *gin.Context) { GetIsUserAuthorized(context) isAuthorized := context.GetBool("is-authorized") log.Println(isAuthorized) if isAuthorized { name := context.Query("name") tags := context.QueryArray("tags") requirements := context.QueryArray("requirements") if name != "" { newTags := []databasemodels.FunctionTag{} log.Println(tags) for _, tag := range tags { newTags = append(newTags, databasecommands.GetDatabaseFunctionTag(GlobalDatabase, tag)) } log.Println(newTags) newRequirements := []databasemodels.Function{} for _, requirement := range requirements { newRequirement := databasecommands.GetDatabaseFunction(GlobalDatabase, requirement) newRequirements = append(newRequirements, newRequirement) } databasecommands.CreateDatabaseFunction(GlobalDatabase, databasemodels.Function{ Name: name, Tags: newTags, Requirements: newRequirements, }) context.Status(http.StatusOK) } else { context.AbortWithStatus(http.StatusBadRequest) } } else { context.AbortWithStatus(http.StatusUnauthorized) } } func CreateDatabaseFunctionTag(context *gin.Context) { GetIsUserAuthorized(context) isAuthorized := context.GetBool("is-authorized") log.Println(isAuthorized) if isAuthorized { name := context.Query("name") if name != "" { databasecommands.CreateDatabaseFunctionTag(GlobalDatabase, databasemodels.FunctionTag{ Name: name, }) context.Status(http.StatusOK) } else { context.AbortWithStatus(http.StatusBadRequest) } } else { context.AbortWithStatus(http.StatusUnauthorized) } } // Read Endpoints (get/) func GetUserInfo(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 databasecommands.GetDatabaseUserLoggedInFromDiscordId(GlobalDatabase, user.Id) { context.JSON(http.StatusOK, user) } else { context.Redirect(http.StatusTemporaryRedirect, "http://localhost:31337/auth/logout") } return } else { log.Println(err) context.AbortWithStatus(http.StatusBadRequest) } } else { log.Println(err) context.AbortWithStatus(http.StatusBadRequest) } } else { log.Println(err) context.AbortWithStatus(http.StatusBadRequest) } context.Redirect(http.StatusTemporaryRedirect, "http://localhost:15995/") } func GetIsUserAuthorized(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() { if databasecommands.GetDatabaseUserLoggedIn(GlobalDatabase, oauthTokenJSON) { context.JSON(http.StatusOK, gin.H{ "message": true, }) context.Set("is-authorized", true) return } } } } context.JSON(http.StatusUnauthorized, gin.H{ "message": false, }) context.Set("is-authorized", false) } func GetDatabaseGroup(context *gin.Context) { groupName := context.Query("group") if groupName != "" { group := databasecommands.GetDatabaseGroup(GlobalDatabase, groupName) context.JSON(http.StatusOK, group) } else { context.JSON(http.StatusBadRequest, gin.H{ "name": "", }) } } func GetDatabaseGroups(context *gin.Context) { groupNames := context.QueryArray("groups") groups := databasecommands.GetDatabaseGroups(GlobalDatabase, groupNames) context.JSON(http.StatusOK, gin.H{ "groups": groups, }) } func GetAllDatabaseGroups(context *gin.Context) { groups := databasecommands.GetAllDatabaseGroups(GlobalDatabase) context.JSON(http.StatusOK, gin.H{ "groups": groups, }) } func GetDatabaseFunction(context *gin.Context) { functionName := context.Query("function") if functionName != "" { group := databasecommands.GetDatabaseFunction(GlobalDatabase, functionName) context.JSON(http.StatusOK, group) } else { context.JSON(http.StatusBadRequest, gin.H{ "name": "", }) } } func GetDatabaseFunctions(context *gin.Context) { functionNames := context.QueryArray("functions") functions := databasecommands.GetDatabaseFunctions(GlobalDatabase, functionNames) context.JSON(http.StatusOK, gin.H{ "functions": functions, }) } func GetAllDatabaseFunctions(context *gin.Context) { functions := databasecommands.GetAllDatabaseFunctions(GlobalDatabase) context.JSON(http.StatusOK, gin.H{ "functions": functions, }) } func GetDatabaseFunctionTag(context *gin.Context) { functionTagName := context.Query("name") if functionTagName != "" { functionTag := databasecommands.GetDatabaseFunctionTag(GlobalDatabase, functionTagName) context.JSON(http.StatusOK, functionTag) } else { context.JSON(http.StatusBadRequest, gin.H{ "name": "", }) } } func GetDatabaseFunctionTags(context *gin.Context) { functionTagNames := context.QueryArray("functiontags") functionTags := databasecommands.GetDatabaseFunctionTags(GlobalDatabase, functionTagNames) context.JSON(http.StatusOK, gin.H{ "function_tags": functionTags, }) } func GetAllDatabaseFunctionTags(context *gin.Context) { functionTags := databasecommands.GetAllDatabaseFunctionTags(GlobalDatabase) context.JSON(http.StatusOK, gin.H{ "function_tags": functionTags, }) } // Delete Endpoints (delete/)