Add setup wiki for discord oauth + nearly finish web interface
This commit is contained in:
parent
0dc1423b9d
commit
8e198dcdd7
|
@ -24,6 +24,7 @@ type CS struct {
|
|||
API struct {
|
||||
Host string
|
||||
Port string
|
||||
FrontendPort string
|
||||
}
|
||||
JWT struct {
|
||||
Secret string
|
||||
|
@ -91,6 +92,11 @@ func LoadConfig(file []byte) {
|
|||
panic("API Port is empty")
|
||||
}
|
||||
|
||||
Config.API.FrontendPort = cfg.Section("api").Key("frontend_port").String()
|
||||
if Config.API.FrontendPort == "" {
|
||||
Config.API.FrontendPort = Config.API.Port
|
||||
}
|
||||
|
||||
Config.JWT.Secret = cfg.Section("jwt").Key("secret").String()
|
||||
if Config.JWT.Secret == "" {
|
||||
panic("JWT Secret is empty")
|
||||
|
|
11
aid/fiber.go
11
aid/fiber.go
|
@ -4,6 +4,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/limiter"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
)
|
||||
|
@ -22,10 +23,8 @@ func FiberLimiter() fiber.Handler {
|
|||
}
|
||||
|
||||
func FiberCors() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
c.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Set("Access-Control-Allow-Headers", "Content-Type, Authorization, Origin, Accept, X-Requested-With")
|
||||
return c.Next()
|
||||
}
|
||||
return cors.New(cors.Config{
|
||||
AllowOrigins: "*",
|
||||
AllowHeaders: "Origin, Content-Type, Accept, Authorization, X-Requested-With",
|
||||
})
|
||||
}
|
|
@ -7,17 +7,17 @@ type="postgres"
|
|||
drop=false
|
||||
|
||||
[discord]
|
||||
; discord oauth2 client id
|
||||
; discord id of the bot
|
||||
id="1234567890..."
|
||||
; discord oauth2 client secret
|
||||
; oauth2 client secret
|
||||
secret="abcdefg..."
|
||||
; discord bot token
|
||||
token="OTK...."
|
||||
|
||||
[output]
|
||||
; level of logging
|
||||
; info = everything
|
||||
; time = only time taken
|
||||
; info = backend logs
|
||||
; time = backend logs + time taken for database queries
|
||||
; prod = only errors
|
||||
level="info"
|
||||
|
||||
|
|
|
@ -1,12 +1,87 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
p "github.com/ectrc/snow/person"
|
||||
"github.com/ectrc/snow/storage"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func GetDiscordOAuthURL(c *fiber.Ctx) error {
|
||||
return c.Status(200).SendString("https://discord.com/api/oauth2/authorize?client_id="+ aid.Config.Discord.ID +"&redirect_uri="+ url.QueryEscape(aid.Config.API.Host + aid.Config.API.Port +"/snow/discord/callback") + "&response_type=code&scope=identify")
|
||||
code := c.Query("code")
|
||||
if code == "" {
|
||||
return c.Status(200).SendString("https://discord.com/oauth2/authorize?client_id="+ aid.Config.Discord.ID +"&redirect_uri="+ url.QueryEscape(aid.Config.API.Host + aid.Config.API.Port +"/snow/discord") + "&response_type=code&scope=identify")
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
oauthRequest, err := client.PostForm("https://discord.com/api/v10/oauth2/token", url.Values{
|
||||
"client_id": {aid.Config.Discord.ID},
|
||||
"client_secret": {aid.Config.Discord.Secret},
|
||||
"grant_type": {"authorization_code"},
|
||||
"code": {code},
|
||||
"redirect_uri": {aid.Config.API.Host + aid.Config.API.Port +"/snow/discord"},
|
||||
})
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(aid.JSON{"error":err.Error()})
|
||||
}
|
||||
|
||||
var body struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RenewToken string `json:"refresh_token"`
|
||||
}
|
||||
err = json.NewDecoder(oauthRequest.Body).Decode(&body)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(aid.JSON{"error":err.Error()})
|
||||
}
|
||||
|
||||
userRequest, err := http.NewRequest("GET", "https://discord.com/api/v10/users/@me", nil)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(aid.JSON{"error":err.Error()})
|
||||
}
|
||||
userRequest.Header.Set("Authorization", "Bearer " + body.AccessToken)
|
||||
|
||||
userResponse, err := client.Do(userRequest)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(aid.JSON{"error":err.Error()})
|
||||
}
|
||||
|
||||
var user struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
err = json.NewDecoder(userResponse.Body).Decode(&user)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(aid.JSON{"error":err.Error()})
|
||||
}
|
||||
|
||||
person := p.FindByDiscord(user.ID)
|
||||
if person == nil {
|
||||
return c.Status(404).JSON(aid.ErrorNotFound)
|
||||
}
|
||||
|
||||
person.Discord.AccessToken = body.AccessToken
|
||||
person.Discord.RefreshToken = body.RenewToken
|
||||
storage.Repo.SaveDiscordPerson(person.Discord)
|
||||
|
||||
access, err := aid.JWTSign(aid.JSON{
|
||||
"snow_id": person.ID, // custom
|
||||
"frontend": true,
|
||||
"creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"),
|
||||
})
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer)
|
||||
}
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "access_token",
|
||||
Value: access,
|
||||
})
|
||||
return c.Redirect(aid.Config.API.Host + aid.Config.API.FrontendPort + "/attempt")
|
||||
}
|
1
main.go
1
main.go
|
@ -120,6 +120,7 @@ func main() {
|
|||
|
||||
player := snow.Group("/player")
|
||||
player.Use(handlers.FrontendMiddleware)
|
||||
player.Post("/", handlers.AnyNoContent)
|
||||
player.Get("/locker", handlers.GetPlayerLocker)
|
||||
|
||||
r.Hooks().OnListen(func(ld fiber.ListenData) error {
|
||||
|
|
|
@ -65,6 +65,20 @@ func (m *PersonsCache) GetPersonByDisplay(displayName string) *Person {
|
|||
return person
|
||||
}
|
||||
|
||||
func (m *PersonsCache) GetPersonByDiscordID(discordId string) *Person {
|
||||
var person *Person
|
||||
m.RangeEntry(func(key string, value *CacheEntry) bool {
|
||||
if value.Entry.Discord.ID == discordId {
|
||||
person = value.Entry
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return person
|
||||
}
|
||||
|
||||
func (m *PersonsCache) SavePerson(p *Person) {
|
||||
m.Store(p.ID, &CacheEntry{
|
||||
Entry: p,
|
||||
|
|
|
@ -149,7 +149,6 @@ func (l *Loadout) GetAttribute(attribute string) interface{} {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
switch attribute {
|
||||
case "locker_name":
|
||||
return l.LockerName
|
||||
|
|
|
@ -15,6 +15,7 @@ type Person struct {
|
|||
Profile0Profile *Profile
|
||||
CollectionsProfile *Profile
|
||||
CreativeProfile *Profile
|
||||
Discord *storage.DB_DiscordPerson
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
|
@ -59,16 +60,34 @@ func FindByDisplay(displayName string) *Person {
|
|||
cache = NewPersonsCacheMutex()
|
||||
}
|
||||
|
||||
cachedPerson := cache.GetPersonByDisplay(displayName)
|
||||
if cachedPerson != nil {
|
||||
return cachedPerson
|
||||
}
|
||||
|
||||
person := storage.Repo.GetPersonByDisplayFromDB(displayName)
|
||||
if person == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cachedPerson := cache.GetPerson(person.ID)
|
||||
return findHelper(person)
|
||||
}
|
||||
|
||||
func FindByDiscord(discordId string) *Person {
|
||||
if cache == nil {
|
||||
cache = NewPersonsCacheMutex()
|
||||
}
|
||||
|
||||
cachedPerson := cache.GetPersonByDiscordID(discordId)
|
||||
if cachedPerson != nil {
|
||||
return cachedPerson
|
||||
}
|
||||
|
||||
person := storage.Repo.GetPersonByDiscordIDFromDB(discordId)
|
||||
if person == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return findHelper(person)
|
||||
}
|
||||
|
||||
|
@ -122,6 +141,7 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
|
|||
Profile0Profile: profile0,
|
||||
CollectionsProfile: collectionsProfile,
|
||||
CreativeProfile: creativeProfile,
|
||||
Discord: &databasePerson.Discord,
|
||||
}
|
||||
|
||||
cache.SavePerson(person)
|
||||
|
@ -182,6 +202,8 @@ func (p *Person) ToDatabase() *storage.DB_Person {
|
|||
DisplayName: p.DisplayName,
|
||||
Profiles: []storage.DB_Profile{},
|
||||
AccessKey: p.AccessKey,
|
||||
Discord: *p.Discord,
|
||||
DiscordID: p.Discord.ID,
|
||||
}
|
||||
|
||||
profilesToConvert := map[string]*Profile{
|
||||
|
@ -260,5 +282,6 @@ func (p *Person) Snapshot() *PersonSnapshot {
|
|||
Profile0Profile: *p.Profile0Profile.Snapshot(),
|
||||
CollectionsProfile: *p.CollectionsProfile.Snapshot(),
|
||||
CreativeProfile: *p.CreativeProfile.Snapshot(),
|
||||
Discord: *p.Discord,
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package person
|
||||
|
||||
import "github.com/ectrc/snow/storage"
|
||||
|
||||
type PersonSnapshot struct {
|
||||
ID string
|
||||
DisplayName string
|
||||
|
@ -9,6 +11,7 @@ type PersonSnapshot struct {
|
|||
Profile0Profile ProfileSnapshot
|
||||
CollectionsProfile ProfileSnapshot
|
||||
CreativeProfile ProfileSnapshot
|
||||
Discord storage.DB_DiscordPerson
|
||||
}
|
||||
|
||||
type ProfileSnapshot struct {
|
||||
|
|
|
@ -12,7 +12,7 @@ type PostgresStorage struct {
|
|||
}
|
||||
|
||||
func NewPostgresStorage() *PostgresStorage {
|
||||
l := logger.Default.LogMode(logger.Silent)
|
||||
l := logger.Default
|
||||
if aid.Config.Output.Level == "time" {
|
||||
l = logger.Default.LogMode(logger.Info)
|
||||
}
|
||||
|
@ -43,6 +43,8 @@ func (s *PostgresStorage) MigrateAll() {
|
|||
s.Migrate(&DB_Loot{}, "Loot")
|
||||
s.Migrate(&DB_VariantChannel{}, "Variants")
|
||||
s.Migrate(&DB_PAttribute{}, "Attributes")
|
||||
s.Migrate(&DB_TemporaryCode{}, "Exchange")
|
||||
s.Migrate(&DB_DiscordPerson{}, "Discord")
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DropTables() {
|
||||
|
@ -52,6 +54,7 @@ func (s *PostgresStorage) DropTables() {
|
|||
func (s *PostgresStorage) GetPerson(personId string) *DB_Person {
|
||||
var dbPerson DB_Person
|
||||
s.Postgres.
|
||||
Model(&DB_Person{}).
|
||||
Preload("Profiles").
|
||||
Preload("Profiles.Loadouts").
|
||||
Preload("Profiles.Items.Variants").
|
||||
|
@ -60,6 +63,7 @@ func (s *PostgresStorage) GetPerson(personId string) *DB_Person {
|
|||
Preload("Profiles.Items").
|
||||
Preload("Profiles.Gifts").
|
||||
Preload("Profiles.Quests").
|
||||
Preload("Discord").
|
||||
Where("id = ?", personId).
|
||||
Find(&dbPerson)
|
||||
|
||||
|
@ -73,6 +77,7 @@ func (s *PostgresStorage) GetPerson(personId string) *DB_Person {
|
|||
func (s *PostgresStorage) GetPersonByDisplay(displayName string) *DB_Person {
|
||||
var dbPerson DB_Person
|
||||
s.Postgres.
|
||||
Model(&DB_Person{}).
|
||||
Preload("Profiles").
|
||||
Preload("Profiles.Loadouts").
|
||||
Preload("Profiles.Items.Variants").
|
||||
|
@ -81,6 +86,7 @@ func (s *PostgresStorage) GetPersonByDisplay(displayName string) *DB_Person {
|
|||
Preload("Profiles.Items").
|
||||
Preload("Profiles.Gifts").
|
||||
Preload("Profiles.Quests").
|
||||
Preload("Discord").
|
||||
Where("display_name = ?", displayName).
|
||||
Find(&dbPerson)
|
||||
|
||||
|
@ -91,10 +97,22 @@ func (s *PostgresStorage) GetPersonByDisplay(displayName string) *DB_Person {
|
|||
return &dbPerson
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) GetPersonByDiscordID(discorId string) *DB_Person {
|
||||
var discordEntry DB_DiscordPerson
|
||||
s.Postgres.Model(&DB_DiscordPerson{}).Where("id = ?", discorId).Find(&discordEntry)
|
||||
|
||||
if discordEntry.ID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.GetPerson(discordEntry.PersonID)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) GetAllPersons() []*DB_Person {
|
||||
var dbPersons []*DB_Person
|
||||
|
||||
s.Postgres.
|
||||
Model(&DB_Person{}).
|
||||
Preload("Profiles").
|
||||
Preload("Profiles.Loadouts").
|
||||
Preload("Profiles.Items.Variants").
|
||||
|
@ -103,6 +121,7 @@ func (s *PostgresStorage) GetAllPersons() []*DB_Person {
|
|||
Preload("Profiles.Items").
|
||||
Preload("Profiles.Gifts").
|
||||
Preload("Profiles.Quests").
|
||||
Preload("Discord").
|
||||
Find(&dbPersons)
|
||||
|
||||
return dbPersons
|
||||
|
@ -179,3 +198,19 @@ func (s *PostgresStorage) SaveLoadout(loadout *DB_Loadout) {
|
|||
func (s *PostgresStorage) DeleteLoadout(loadoutId string) {
|
||||
s.Postgres.Delete(&DB_Loadout{}, "id = ?", loadoutId)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) SaveTemporaryCode(code *DB_TemporaryCode) {
|
||||
s.Postgres.Save(code)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DeleteTemporaryCode(codeId string) {
|
||||
s.Postgres.Delete(&DB_TemporaryCode{}, "id = ?", codeId)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) SaveDiscordPerson(discordPerson *DB_DiscordPerson) {
|
||||
s.Postgres.Save(discordPerson)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DeleteDiscordPerson(discordPersonId string) {
|
||||
s.Postgres.Delete(&DB_DiscordPerson{}, "id = ?", discordPersonId)
|
||||
}
|
|
@ -9,6 +9,7 @@ type Storage interface {
|
|||
|
||||
GetPerson(personId string) *DB_Person
|
||||
GetPersonByDisplay(displayName string) *DB_Person
|
||||
GetPersonByDiscordID(discordId string) *DB_Person
|
||||
GetAllPersons() []*DB_Person
|
||||
SavePerson(person *DB_Person)
|
||||
|
||||
|
@ -35,6 +36,12 @@ type Storage interface {
|
|||
|
||||
SaveLoadout(loadout *DB_Loadout)
|
||||
DeleteLoadout(loadoutId string)
|
||||
|
||||
SaveTemporaryCode(code *DB_TemporaryCode)
|
||||
DeleteTemporaryCode(codeId string)
|
||||
|
||||
SaveDiscordPerson(person *DB_DiscordPerson)
|
||||
DeleteDiscordPerson(personId string)
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
|
@ -65,6 +72,15 @@ func (r *Repository) GetPersonByDisplayFromDB(displayName string) *DB_Person {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetPersonByDiscordIDFromDB(discordId string) *DB_Person {
|
||||
storagePerson := r.Storage.GetPersonByDiscordID(discordId)
|
||||
if storagePerson != nil {
|
||||
return storagePerson
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetAllPersons() []*DB_Person {
|
||||
return r.Storage.GetAllPersons()
|
||||
}
|
||||
|
@ -136,3 +152,19 @@ func (r *Repository) SaveLoadout(loadout *DB_Loadout) {
|
|||
func (r *Repository) DeleteLoadout(loadoutId string) {
|
||||
r.Storage.DeleteLoadout(loadoutId)
|
||||
}
|
||||
|
||||
func (r *Repository) SaveTemporaryCode(code *DB_TemporaryCode) {
|
||||
r.Storage.SaveTemporaryCode(code)
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteTemporaryCode(codeId string) {
|
||||
r.Storage.DeleteTemporaryCode(codeId)
|
||||
}
|
||||
|
||||
func (r *Repository) SaveDiscordPerson(person *DB_DiscordPerson) {
|
||||
r.Storage.SaveDiscordPerson(person)
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteDiscordPerson(personId string) {
|
||||
r.Storage.DeleteDiscordPerson(personId)
|
||||
}
|
|
@ -11,6 +11,8 @@ type DB_Person struct {
|
|||
DisplayName string
|
||||
AccessKey string
|
||||
Profiles []DB_Profile `gorm:"foreignkey:PersonID"`
|
||||
Discord DB_DiscordPerson `gorm:"foreignkey:PersonID"`
|
||||
DiscordID string
|
||||
}
|
||||
|
||||
func (DB_Person) TableName() string {
|
||||
|
@ -26,7 +28,7 @@ type DB_Profile struct {
|
|||
Attributes []DB_PAttribute `gorm:"foreignkey:ProfileID"`
|
||||
Loadouts []DB_Loadout `gorm:"foreignkey:ProfileID"`
|
||||
Type string
|
||||
Revision int
|
||||
Revision int
|
||||
}
|
||||
|
||||
func (DB_Profile) TableName() string {
|
||||
|
@ -134,3 +136,26 @@ type DB_Loot struct {
|
|||
func (DB_Loot) TableName() string {
|
||||
return "Loot"
|
||||
}
|
||||
|
||||
type DB_TemporaryCode struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
Code string
|
||||
ExpiresAt int64
|
||||
PersonID string
|
||||
}
|
||||
|
||||
func (DB_TemporaryCode) TableName() string {
|
||||
return "Exchange"
|
||||
}
|
||||
|
||||
type DB_DiscordPerson struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
PersonID string
|
||||
Username string
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
func (DB_DiscordPerson) TableName() string {
|
||||
return "Discord"
|
||||
}
|
50
wiki/oauth.md
Normal file
50
wiki/oauth.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Configure Discord OAuth
|
||||
|
||||
## Getting your OAuth Credentials
|
||||
|
||||
![Alt text](oauth1.png)
|
||||
|
||||
![Alt text](oauth2.png)
|
||||
|
||||
Part of the file `config.ini` should look like this:
|
||||
|
||||
```ini
|
||||
[discord]
|
||||
; discord id of the bot
|
||||
id="1234567890..."
|
||||
; oauth2 client secret
|
||||
secret="abcdefg..."
|
||||
; discord bot token
|
||||
token="OTK...."
|
||||
```
|
||||
|
||||
Replace the values with your own, save and rebuild to apply the changes.
|
||||
|
||||
## Setup the bot
|
||||
|
||||
Add the correct redirects to your discord application:
|
||||
|
||||
![Alt text](redirects.png)
|
||||
|
||||
This will be from the `config.ini` file:
|
||||
|
||||
```ini
|
||||
[api]
|
||||
port=":3000"
|
||||
host="http://localhost"
|
||||
```
|
||||
|
||||
Make sure to add `/snow/discord` to the end of the redirect url.
|
||||
|
||||
## Inviting the bot
|
||||
|
||||
Generate an invite link for the bot with the following permissions:
|
||||
|
||||
![Alt text](scopes1.png)
|
||||
![Alt text](scopes2.png)
|
||||
|
||||
The invite link should look like this:
|
||||
|
||||
```url
|
||||
https://discord.com/api/oauth2/authorize?client_id=CLIENT_ID&permissions=34816&redirect_uri=CALLBACK_URL&scope=bot+applications.commands
|
||||
```
|
BIN
wiki/oauth1.png
Normal file
BIN
wiki/oauth1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
BIN
wiki/oauth2.png
Normal file
BIN
wiki/oauth2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
wiki/redirects.png
Normal file
BIN
wiki/redirects.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
wiki/scopes1.png
Normal file
BIN
wiki/scopes1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
BIN
wiki/scopes2.png
Normal file
BIN
wiki/scopes2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 102 KiB |
Loading…
Reference in New Issue
Block a user