OAuth keys are saved as cookies and added to database.

This commit is contained in:
Ada Werefox 2025-04-22 14:21:40 -07:00
parent ccb06f2184
commit dc55b6f19a
7 changed files with 159 additions and 63 deletions

9
config.sample.toml Normal file
View 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]"

View file

@ -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")
}

View file

@ -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
}

View file

@ -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

View file

@ -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=

View file

@ -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>

View file

@ -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>