diff --git a/config.sample.toml b/config.sample.toml index 328f7b2..9b808e8 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -3,6 +3,11 @@ domain = "localhost" port = "31337" https = false +[frontend] +domain = "localhost" +port = "15995" +https = false + [oauth] # You can get all of this from https://discord.com/developers/applications clientid = "[CHANGE ME]" diff --git a/src/gin-cpularp.go b/src/gin-cpularp.go index 471f562..0e236b1 100755 --- a/src/gin-cpularp.go +++ b/src/gin-cpularp.go @@ -18,7 +18,6 @@ func main() { router.GET("/auth/login", api.AuthLoginRedirect) router.GET("/auth/logout", api.AuthLogoutRedirect) // Create - router.POST("/user/update", api.CreateOrUpdateUser) router.POST("/group", api.CreateGroup) router.POST("/function", api.CreateFunction) router.POST("/function-tag", api.CreateFunctionTag) diff --git a/src/lib/api/api.go b/src/lib/api/api.go index a05bb1d..5118736 100644 --- a/src/lib/api/api.go +++ b/src/lib/api/api.go @@ -36,6 +36,97 @@ var GlobalOAuth *oauth2.Config // Private Functions +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) + } + var currentDiscordUser authdiscord.DiscordUser + result := currentDiscordUser.Get(context, oauthToken, GlobalOAuth) + if result == "" { + if user.Exists(GlobalDatabase, currentDiscordUser.Id) { + currentUser := (*user.Get(GlobalDatabase, []string{currentDiscordUser.Id}))[0] + err := error(nil) + if currentUser.LoggedIn { + context.SetCookie("discord-oauthtoken", currentUser.LoginToken, 0, "", GlobalConfig.API.Domain, false, false) + err = user.Update( + GlobalDatabase, + currentDiscordUser.Id, + currentDiscordUser.Global_Name, + currentDiscordUser.Username, + currentDiscordUser.Avatar, + currentDiscordUser.Avatar_Decoration_Data.Asset, + currentUser.LoginToken, + true, + ) + } else { + // context.SetCookie("discord-oauthtoken", oauthTokenJSON, 0, "", GlobalConfig.API.Domain, false, false) + err = user.Update( + GlobalDatabase, + currentDiscordUser.Id, + currentDiscordUser.Global_Name, + currentDiscordUser.Username, + currentDiscordUser.Avatar, + currentDiscordUser.Avatar_Decoration_Data.Asset, + oauthTokenJSON, + true, + ) + } + if err != nil { + log.Println(err) + context.AbortWithStatus(http.StatusInternalServerError) + } + } else { + // context.SetCookie("discord-oauthtoken", oauthTokenJSON, 0, "", GlobalConfig.API.Domain, false, false) + err := user.Create( + GlobalDatabase, + currentDiscordUser.Id, + currentDiscordUser.Global_Name, + currentDiscordUser.Username, + currentDiscordUser.Avatar, + currentDiscordUser.Avatar_Decoration_Data.Asset, + oauthTokenJSON, + true, + ) + if err != nil { + log.Println(err) + context.AbortWithStatus(http.StatusInternalServerError) + } + } + } else { + log.Printf("This also really shouldn't happen. %s", result) + context.AbortWithStatus(http.StatusInternalServerError) + } +} + +func checkAuthentication(context *gin.Context) *oauth2.Token { + 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 (*user.Get(GlobalDatabase, []string{oauthTokenJSON}))[0].LoggedIn { + return oauthToken + } + } + } + } + context.Redirect(http.StatusTemporaryRedirect, GlobalConfig.GetFrontendRootDomain()) + return nil +} + func objectIDStringsToInts(context *gin.Context, objectIDs []string) *[]int { var objectIDInts []int for _, objectID := range objectIDs { @@ -62,12 +153,13 @@ func AuthCallback(context *gin.Context) { oauthTokenJSON, _ := json.Marshal(oauthToken) context.SetCookie("discord-oauthtoken", string(oauthTokenJSON), 0, "", GlobalConfig.API.Domain, false, false) context.Set("discord-oauthtoken", string(oauthTokenJSON)) - CreateOrUpdateUser(context) + createOrUpdateUser(context) + log.Println(GlobalConfig.GetFrontendRootDomain()) context.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%sdashboard", GlobalConfig.GetFrontendRootDomain())) } func AuthLoginRedirect(context *gin.Context) { - context.Redirect(302, GlobalOAuth.AuthCodeURL(context.GetString("state"))) + context.Redirect(http.StatusTemporaryRedirect, GlobalOAuth.AuthCodeURL(context.GetString("state"))) } func AuthLogoutRedirect(context *gin.Context) { @@ -78,190 +170,96 @@ func AuthLogoutRedirect(context *gin.Context) { } else { log.Println(err) } + log.Println(GlobalConfig.GetFrontendRootDomain()) context.Redirect(http.StatusTemporaryRedirect, GlobalConfig.GetFrontendRootDomain()) } -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) - } - var currentDiscordUser authdiscord.DiscordUser - currentDiscordUser.GetDiscordUser(context, oauthToken, GlobalConfig, GlobalOAuth) - updatedDatabaseUser := user.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 user.Exists(GlobalDatabase, currentDiscordUser.Id) { - dbOAuthToken := user.GetTokenFromUserId(GlobalDatabase, currentDiscordUser.Id) - if user.GetLoggedInFromDiscordId(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 := updatedDatabaseUser.Update(GlobalDatabase) - if err != nil { - log.Println(err) - context.AbortWithStatus(http.StatusInternalServerError) - } - } else { - err := updatedDatabaseUser.Create(GlobalDatabase) - if err != nil { - log.Println(err) - context.Copy().AbortWithStatus(http.StatusInternalServerError) - } - } -} - -// Create Endpoints (post/) +// Create Endpoints (POST) func CreateGroup(context *gin.Context) { - GetUserLoggedIn(context) - isAuthorized := context.GetBool("is-authorized") - if isAuthorized { - name, nameOk := context.GetQuery("name") - if nameOk { - group.Create(GlobalDatabase, name) - context.Status(http.StatusOK) - } else { - context.AbortWithStatus(http.StatusBadRequest) - } + checkAuthentication(context) + name, nameOk := context.GetQuery("name") + if nameOk { + group.Create(GlobalDatabase, name) + context.Status(http.StatusOK) } else { - context.AbortWithStatus(http.StatusUnauthorized) + context.AbortWithStatus(http.StatusBadRequest) } } func CreateFunction(context *gin.Context) { - GetUserLoggedIn(context) - isAuthorized := context.GetBool("is-authorized") - if isAuthorized { - name, nameOk := context.GetQuery("name") - tags := context.QueryArray("tags") - requirements := context.QueryArray("requirements") - if nameOk { - function.Create(GlobalDatabase, name, tags, requirements) - context.Status(http.StatusOK) - } else { - context.AbortWithStatus(http.StatusBadRequest) - } + checkAuthentication(context) + name, nameOk := context.GetQuery("name") + tags := context.QueryArray("tags") + requirements := context.QueryArray("requirements") + if nameOk { + function.Create(GlobalDatabase, name, tags, requirements) + context.Status(http.StatusOK) } else { - context.AbortWithStatus(http.StatusUnauthorized) + context.AbortWithStatus(http.StatusBadRequest) } } func CreateFunctionTag(context *gin.Context) { - GetUserLoggedIn(context) - isAuthorized := context.GetBool("is-authorized") - if isAuthorized { - name := context.Query("name") - if name != "" { - functiontag.Create(GlobalDatabase, name) - context.Status(http.StatusOK) - } else { - context.AbortWithStatus(http.StatusBadRequest) - } + checkAuthentication(context) + name := context.Query("name") + if name != "" { + functiontag.Create(GlobalDatabase, name) + context.Status(http.StatusOK) } else { - context.AbortWithStatus(http.StatusUnauthorized) - } -} - -// Update Endpoints (put/) - -func UpdateFunction(context *gin.Context) { - GetUserLoggedIn(context) - isAuthorized := context.GetBool("is-authorized") - if isAuthorized { - name, nameOk := context.GetQuery("name") - tags := context.QueryArray("tags") - requirements := context.QueryArray("requirements") - if nameOk { - function.Update(GlobalDatabase, name, tags, requirements) - context.Status(http.StatusOK) - } else { - context.AbortWithStatus(http.StatusBadRequest) - } - } else { - context.AbortWithStatus(http.StatusUnauthorized) - } -} - -// Read Endpoints (get/) - -func GetDiscordUser(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() { - var discordUser authdiscord.DiscordUser - discordUser.GetDiscordUser(context, oauthToken, GlobalConfig, GlobalOAuth) - if user.GetLoggedInFromDiscordId(GlobalDatabase, discordUser.Id) { - context.JSON(http.StatusOK, discordUser) - } else { - context.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%slogout", GlobalConfig.GetAPIRootDomain())) - } - 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, GlobalConfig.GetFrontendRootDomain()) +} + +// Update Endpoints (PUT) + +func UpdateFunction(context *gin.Context) { + checkAuthentication(context) + name, nameOk := context.GetQuery("name") + tags := context.QueryArray("tags") + requirements := context.QueryArray("requirements") + if nameOk { + function.Update(GlobalDatabase, name, tags, requirements) + context.Status(http.StatusOK) + } else { + context.AbortWithStatus(http.StatusBadRequest) + } +} + +// Read Endpoints (GET) + +func GetDiscordUser(context *gin.Context) { + oauthToken := checkAuthentication(context) + if oauthToken != nil { + var discordUser authdiscord.DiscordUser + result := discordUser.Get(context, oauthToken, GlobalOAuth) + if result != "" { + log.Println(result) + log.Println("Assuming the Discord OAuth Key has expired.") + context.Redirect(http.StatusUnauthorized, fmt.Sprintf("%slogout", GlobalConfig.GetFrontendRootDomain())) + } else { + if (*user.Get(GlobalDatabase, []string{discordUser.Id}))[0].LoggedIn { + context.JSON(http.StatusOK, discordUser) + } else { + context.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%slogout", GlobalConfig.GetFrontendRootDomain())) + } + } + } } func GetUserLoggedIn(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 user.GetLoggedInFromOAuthToken(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.JSON(http.StatusOK, gin.H{ + "message": (checkAuthentication(context) != nil), }) - context.Set("is-authorized", false) } func GetObjects(context *gin.Context) { objectIDs, idOk := context.GetQueryArray("id") if idOk { switch objectType := context.Param("object"); objectType { + case "user": + context.JSON(http.StatusOK, gin.H{ + "user": user.Get(GlobalDatabase, objectIDs)}) case "persons": context.JSON(http.StatusOK, gin.H{ "persons": person.Get(GlobalDatabase, objectIDs), @@ -381,20 +379,15 @@ func GetAllObjects(context *gin.Context) { } } -// Delete Endpoints (delete/) +// Delete Endpoints (DELETE) func DeleteFunction(context *gin.Context) { - GetUserLoggedIn(context) - isAuthorized := context.GetBool("is-authorized") - if isAuthorized { - functionNames, ok := context.GetQueryArray("name") - if ok { - function.Delete(GlobalDatabase, functionNames) - context.Status(http.StatusOK) - } else { - context.AbortWithStatus(http.StatusBadRequest) - } + checkAuthentication(context) + functionNames, ok := context.GetQueryArray("name") + if ok { + function.Delete(GlobalDatabase, functionNames) + context.Status(http.StatusOK) } else { - context.AbortWithStatus(http.StatusUnauthorized) + context.AbortWithStatus(http.StatusBadRequest) } } diff --git a/src/lib/auth/discord/discord.go b/src/lib/auth/discord/discord.go index e4f2543..8ded177 100644 --- a/src/lib/auth/discord/discord.go +++ b/src/lib/auth/discord/discord.go @@ -4,8 +4,6 @@ import ( "encoding/json" "fmt" "io" - "log" - "net/http" configserver "example.com/config/server" @@ -38,25 +36,22 @@ type DiscordUser struct { Premium_Type float64 `json:"premium_type"` } -func (discordUser *DiscordUser) GetDiscordUser(context *gin.Context, oauthToken *oauth2.Token, appConfig configserver.AppConfig, oauthConfig *oauth2.Config) { +func (discordUser *DiscordUser) Get(context *gin.Context, oauthToken *oauth2.Token, oauthConfig *oauth2.Config) string { response, err := oauthConfig.Client(context, oauthToken).Get("https://discord.com/api/users/@me") if err != nil || response.StatusCode != 200 { - responseMessage := "" if err != nil { - responseMessage = err.Error() + return err.Error() } else { - responseMessage = response.Status + return response.Status } - log.Println(responseMessage) - log.Println("Assuming the Discord OAuth Key has expired.") - context.Redirect(http.StatusUnauthorized, fmt.Sprintf("%slogout", appConfig.GetFrontendRootDomain())) } defer response.Body.Close() body, err := io.ReadAll(response.Body) if err != nil { - log.Println(err) + return err.Error() } json.Unmarshal(body, &discordUser) + return "" } func CreateDiscordOAuthConfig(config configserver.AppConfig) *oauth2.Config { diff --git a/src/lib/database/user/user.go b/src/lib/database/user/user.go index 86363a4..0e95b74 100644 --- a/src/lib/database/user/user.go +++ b/src/lib/database/user/user.go @@ -1,38 +1,26 @@ package user import ( - "errors" + "log" "gorm.io/gorm" ) type User struct { gorm.Model - Id string `json:"id"` + Id string `gorm:"primaryKey uniqueIndex" json:"id"` DisplayName string `json:"display_name"` Username string `json:"user_name"` Avatar string `json:"avatar"` AvatarDecoration string `json:"avatar_decoration"` LoginToken string `json:"login_token"` LoggedIn bool `json:"logged_in"` - ApiKey []struct { - Name string `gorm:"primaryKey uniqueIndex" json:"name"` + ApiKeys []struct { + Name string `json:"name"` Key string `json:"key"` } `gorm:"foreignKey:Name" json:"api_key"` } -func (user *User) Get(db *gorm.DB, id string) { - db.Where("id = ?", id).Take(&user) -} - -func (user User) Update(db *gorm.DB) error { - result := db.Save(&user) - if result.Error != nil { - return result.Error - } - return nil -} - func (user User) Create(db *gorm.DB) error { result := db.Create(&user) if result.Error != nil { @@ -41,46 +29,96 @@ func (user User) Create(db *gorm.DB) error { return nil } -func Logout(db *gorm.DB, oauthToken string) { - db.Model(&User{}).Where("login_token = ?", oauthToken).Update("logged_in", false) +func (user *User) Get(db *gorm.DB, id string) error { + result := db.Where("id = ?", id).Take(&user) + if result.Error == gorm.ErrRecordNotFound { + result := db.Where("login_token = ?", id).Take(&user) + if result.Error != nil { + return result.Error + } + } + return nil +} + +func (user User) Update(db *gorm.DB) error { + var originalUser User + originalUser.Get(db, user.Id) + result := db.Model(&originalUser).Updates(&user) + if result.Error != nil { + return result.Error + } + return nil +} + +func (user User) Delete(db *gorm.DB) error { + result := db.Delete(&user) + if result.Error != nil { + return result.Error + } + return nil +} + +func (user User) Logout(db *gorm.DB) error { + result := db.Model(&user).Update("logged_in", false) + if result.Error != nil { + return result.Error + } + return nil +} + +func Logout(db *gorm.DB, oauthToken string) error { + var user User + user.Get(db, oauthToken) + return user.Logout(db) } func Exists(db *gorm.DB, id string) bool { var queryUser User - result := db.Where("id = ?", id).Take(&queryUser) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return false - } else { - return true - } + err := queryUser.Get(db, id) + return (err == nil) } -func GetTokenFromUserId(db *gorm.DB, id string) string { - var dbUser User - result := db.Where("id = ?", id).Select("login_token").Take(&dbUser) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return "" - } else { - return dbUser.LoginToken - } +func Create(db *gorm.DB, id string, displayName string, username string, avatar string, avatarDecoration string, loginToken string, loggedIn bool) error { + return User{ + Id: id, + DisplayName: displayName, + Username: username, + Avatar: avatar, + AvatarDecoration: avatarDecoration, + LoginToken: loginToken, + LoggedIn: loggedIn, + ApiKeys: nil, + }.Create(db) } -func GetLoggedInFromOAuthToken(db *gorm.DB, oauthTokenJSON string) bool { - var queryUser User - result := db.Where("login_token = ?", oauthTokenJSON).Take(&queryUser) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return false - } else { - return queryUser.LoggedIn +func Get(db *gorm.DB, inputUsers []string) *[]User { + var outputUsers []User + for _, inputUser := range inputUsers { + var outputUser User + outputUser.Get(db, inputUser) + outputUsers = append(outputUsers, outputUser) } + return &outputUsers } -func GetLoggedInFromDiscordId(db *gorm.DB, discordId string) bool { - var queryUser User - result := db.Where("id = ?", discordId).Take(&queryUser) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return false - } else { - return queryUser.LoggedIn +func GetAll(db *gorm.DB) *[]User { + var outputUserIDs []string + result := db.Model(&User{}).Select("id").Find(&outputUserIDs) + if result.Error != nil { + log.Println(result.Error) } + return Get(db, outputUserIDs) +} + +func Update(db *gorm.DB, id string, displayName string, username string, avatar string, avatarDecoration string, loginToken string, loggedIn bool) error { + return User{ + Id: id, + DisplayName: displayName, + Username: username, + Avatar: avatar, + AvatarDecoration: avatarDecoration, + LoginToken: loginToken, + LoggedIn: loggedIn, + ApiKeys: nil, + }.Update(db) }