OAuth keys are saved as cookies and added to database.
This commit is contained in:
parent
ccb06f2184
commit
dc55b6f19a
7 changed files with 159 additions and 63 deletions
9
config.sample.toml
Normal file
9
config.sample.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[api]
|
||||
domain = "localhost"
|
||||
port = "31337"
|
||||
https = false
|
||||
|
||||
[oauth]
|
||||
# You can get all of this from https://discord.com/developers/applications
|
||||
clientid = "[CHANGE ME]"
|
||||
clientsecret = "[CHANGE ME]"
|
152
gin-cpularp.go
152
gin-cpularp.go
|
@ -2,25 +2,30 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
// "example.com/lib/dbcommands"
|
||||
"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
|
||||
RedirectUrl string
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,11 +53,11 @@ type discordUser struct {
|
|||
Premium_Type float64
|
||||
}
|
||||
|
||||
var db *gorm.DB
|
||||
var config appConfig
|
||||
var oauthConfig *oauth2.Config
|
||||
var oauthToken *oauth2.Token
|
||||
|
||||
func parseConfig(configPath string) {
|
||||
func parseConfig(configPath string) appConfig {
|
||||
configFile, err := os.Open(configPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -61,17 +66,28 @@ func parseConfig(configPath string) {
|
|||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = toml.Unmarshal(configFileContent, &config)
|
||||
var configObject appConfig
|
||||
err = toml.Unmarshal(configFileContent, &configObject)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return configObject
|
||||
}
|
||||
|
||||
func createDiscordOAuthConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
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: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
ClientID: config.OAuth.ClientID,
|
||||
ClientSecret: config.OAuth.ClientSecret,
|
||||
RedirectURL: redirectUrlString,
|
||||
Scopes: []string{discord.ScopeIdentify},
|
||||
Endpoint: discord.Endpoint,
|
||||
}
|
||||
|
@ -79,13 +95,19 @@ func createDiscordOAuthConfig(clientId string, clientSecret string, redirectUrl
|
|||
}
|
||||
|
||||
func loginDisplay(context *gin.Context) {
|
||||
if oauthToken.Valid() {
|
||||
context.Redirect(http.StatusTemporaryRedirect, "/dashboard")
|
||||
} else {
|
||||
context.HTML(http.StatusOK, "root.html", gin.H{
|
||||
"message": "hello, world!",
|
||||
})
|
||||
oauthTokenString, err := context.Cookie("discord-oauthtoken")
|
||||
if err == nil {
|
||||
var oauthToken *oauth2.Token
|
||||
err := json.Unmarshal([]byte(oauthTokenString), &oauthToken)
|
||||
if err == nil {
|
||||
if oauthToken.Valid() {
|
||||
context.Redirect(http.StatusTemporaryRedirect, "/dashboard")
|
||||
}
|
||||
}
|
||||
}
|
||||
context.HTML(http.StatusOK, "root.html", gin.H{
|
||||
"message": "Please log in.",
|
||||
})
|
||||
}
|
||||
|
||||
func loginRedirect(context *gin.Context) {
|
||||
|
@ -96,17 +118,19 @@ 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"))
|
||||
oauthToken, err := oauthConfig.Exchange(context.Request.Context(), context.Query("code"))
|
||||
if err != nil {
|
||||
|
||||
context.HTML(http.StatusInternalServerError, "internalservererror.html", gin.H{
|
||||
"message": "internalservererror",
|
||||
})
|
||||
}
|
||||
oauthTokenJSON, _ := json.Marshal(oauthToken)
|
||||
context.SetCookie("discord-oauthtoken", string(oauthTokenJSON), 0, "", config.API.Domain, false, false)
|
||||
context.Redirect(http.StatusTemporaryRedirect, "/dashboard")
|
||||
}
|
||||
|
||||
func getDiscordUserInfo(context *gin.Context) discordUser {
|
||||
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 := ""
|
||||
|
@ -126,48 +150,68 @@ func getDiscordUserInfo(context *gin.Context) discordUser {
|
|||
"message": err.Error(),
|
||||
})
|
||||
}
|
||||
var discordUserInfo discordUser
|
||||
json.Unmarshal(body, &discordUserInfo)
|
||||
return discordUserInfo
|
||||
var user discordUser
|
||||
json.Unmarshal(body, &user)
|
||||
return user
|
||||
}
|
||||
|
||||
func dashboardDisplay(context *gin.Context) {
|
||||
if oauthToken.Valid() {
|
||||
userInfo := getDiscordUserInfo(context)
|
||||
context.HTML(http.StatusOK, "dashboard.html", gin.H{
|
||||
"discordUser": gin.H{
|
||||
"id": userInfo.Id,
|
||||
"username": userInfo.Username,
|
||||
"avatar": userInfo.Avatar,
|
||||
"discriminator": userInfo.Discriminator,
|
||||
"public_flags": userInfo.Public_Flags,
|
||||
"flags": userInfo.Flags,
|
||||
"banner": userInfo.Banner,
|
||||
"accent_color": strconv.FormatFloat(userInfo.Accent_Color, 'f', 0, 64),
|
||||
"global_name": userInfo.Global_Name,
|
||||
"avatar_decoration_data": gin.H{
|
||||
"asset": userInfo.Avatar_Decoration_Data.Asset,
|
||||
"sku_id": userInfo.Avatar_Decoration_Data.Sku_Id,
|
||||
"expires_at": userInfo.Avatar_Decoration_Data.Expires_At,
|
||||
},
|
||||
"collectibles": userInfo.Collectibles,
|
||||
"banner_color": userInfo.Banner_Color,
|
||||
"clan": userInfo.Clan,
|
||||
"primary_guild": userInfo.Primary_Guild,
|
||||
"mfa_enabled": userInfo.Mfa_Eneabled,
|
||||
"locale": userInfo.Locale,
|
||||
"premium_type": userInfo.Premium_Type,
|
||||
},
|
||||
})
|
||||
func createOrUpdateUser(context *gin.Context, oauthToken *oauth2.Token) {
|
||||
user := getDiscordUser(context, oauthToken)
|
||||
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),
|
||||
}
|
||||
if dbcommands.DatabaseUserExists(db, user.Id) {
|
||||
dbOAuthToken := dbcommands.GetDatabaseUserToken(db, user.Id)
|
||||
if dbOAuthToken == "" {
|
||||
context.SetCookie("discord-oauthtoken", dbOAuthToken, 0, "", config.API.Domain, false, false)
|
||||
dbUser.LoginToken = dbOAuthToken
|
||||
}
|
||||
err := dbcommands.UpdateDatabaseUser(db, dbUser)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
} else {
|
||||
context.Redirect(http.StatusTemporaryRedirect, "/")
|
||||
err := dbcommands.CreateDatabaseUser(db, dbUser)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dashboardDisplay(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() {
|
||||
createOrUpdateUser(context, oauthToken)
|
||||
user := getDiscordUser(context, oauthToken)
|
||||
context.HTML(http.StatusOK, "dashboard.html", user)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Println(err)
|
||||
}
|
||||
} else {
|
||||
log.Println(err)
|
||||
}
|
||||
context.Redirect(http.StatusTemporaryRedirect, "/")
|
||||
}
|
||||
|
||||
func main() {
|
||||
// db := dbcommands.InitializeDatabase()
|
||||
parseConfig("config.toml")
|
||||
oauthConfig = createDiscordOAuthConfig(config.OAuth.ClientID, config.OAuth.ClientSecret, config.OAuth.RedirectUrl)
|
||||
db = dbcommands.InitializeDatabase()
|
||||
config = parseConfig("config.toml")
|
||||
oauthConfig = createDiscordOAuthConfig()
|
||||
app := gin.Default()
|
||||
app.LoadHTMLGlob("src/templates/*.html")
|
||||
app.Static("/public", "./public")
|
||||
|
@ -175,5 +219,5 @@ func main() {
|
|||
app.GET("/login", loginRedirect)
|
||||
app.GET("/auth/callback", authCallback)
|
||||
app.GET("/dashboard", dashboardDisplay)
|
||||
app.Run(":8080")
|
||||
app.Run(":31337")
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dbcommands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
|
@ -9,9 +10,12 @@ import (
|
|||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Id string
|
||||
LoginToken string
|
||||
Id string
|
||||
DisplayName string
|
||||
Username string
|
||||
Avatar string
|
||||
AvatarDecoration string
|
||||
LoginToken string
|
||||
}
|
||||
|
||||
func InitializeDatabase() *gorm.DB {
|
||||
|
@ -22,3 +26,39 @@ func InitializeDatabase() *gorm.DB {
|
|||
db.AutoMigrate(&User{})
|
||||
return db
|
||||
}
|
||||
|
||||
func GetDatabaseUserToken(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 DatabaseUserExists(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
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateDatabaseUser(db *gorm.DB, user User) error {
|
||||
result := db.Save(&user)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateDatabaseUser(db *gorm.DB, user User) error {
|
||||
result := db.Create(&user)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ require (
|
|||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
golang.org/x/oauth2 v0.29.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gorm.io/driver/sqlite v1.5.7 // indirect
|
||||
gorm.io/gorm v1.25.12 // indirect
|
||||
|
|
|
@ -4,6 +4,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="p-2 bg-secondary md:p-4 ring-2 ring-secondary/80">
|
||||
{{ template "userinfo.html" .discordUser }}
|
||||
{{ template "userinfo.html" . }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
<div class="flex flex-row w-full h-full p-4 space-x-4 rounded-sm">
|
||||
<div class="md:min-w-fit md:min-h-fit md:max-w-fit md:max-h-fit max-w-32 max-h-32">
|
||||
<img class="fixed z-1 max-w-[96px] max-h-[96px] md:max-w-[128px] md:max-h-[128px]"
|
||||
src="https://cdn.discordapp.com/avatar-decoration-presets/{{ .avatar_decoration_data.asset }}.png" />
|
||||
src="https://cdn.discordapp.com/avatar-decoration-presets/{{ .Avatar_Decoration_Data.Asset }}.png" />
|
||||
<img class="max-w-[96px] max-h-[96px] mask-clip-border rounded-full md:max-w-[128px] md:max-h-[128px] drop-shadow-accent drop-shadow-md border-8 border-accent"
|
||||
src="https://cdn.discordapp.com/avatars/{{ .id }}/{{ .avatar }}.png" />
|
||||
src="https://cdn.discordapp.com/avatars/{{ .Id }}/{{ .Avatar }}.png" />
|
||||
</div>
|
||||
<div class="w-full m-auto drop-shadow-md drop-shadow-accent text-accent">
|
||||
<p class="text-xl md:text-3xl">
|
||||
{{ .global_name }}
|
||||
{{ .Global_Name }}
|
||||
</p>
|
||||
<a class="ml-2 text-white text-md md:text-xl hover:text-gray-300" href="https://discord.com/users/{{ .id }}"
|
||||
<a class="ml-2 text-white text-md md:text-xl hover:text-gray-300" href="https://discord.com/users/{{ .Id }}"
|
||||
target="_blank">
|
||||
@{{ .username }}
|
||||
@{{ .Username }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue