Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
BuckarooBanzay committed Dec 20, 2023
0 parents commit 51df390
Show file tree
Hide file tree
Showing 15 changed files with 703 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10

- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: test

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.21.x

- name: Checkout code
uses: actions/checkout@v4

- name: Test
run: |
go test ./... -coverprofile=profile.cov
- uses: shogo82148/[email protected]
with:
path-to-profile: profile.cov
121 changes: 121 additions & 0 deletions cdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package oauth

import (
"bytes"
"encoding/json"
"fmt"
"mime/multipart"
"net/http"
"net/url"
)

type CDBUserResponse struct {
Username string `json:"username"`
}

type CDBUser struct {
Username string `json:"username"`
DisplayName string `json:"display_name"`
ProfilePicURL string `json:"profile_pic_url"` // "/uploads/xyz.jpg"
}

type CDBOauth struct{}

func (o *CDBOauth) LoginURL(cfg *OAuthConfig) string {
return fmt.Sprintf("https://content.minetest.net/oauth/authorize/?response_type=code&client_id=%s&redirect_uri=%s", cfg.ClientID, url.QueryEscape(cfg.CallbackURL))
}

func (o *CDBOauth) RequestAccessToken(code string, cfg *OAuthConfig) (string, error) {
var data bytes.Buffer
w := multipart.NewWriter(&data)
w.WriteField("grant_type", "authorization_code")
w.WriteField("client_id", cfg.ClientID)
w.WriteField("client_secret", cfg.Secret)
w.WriteField("code", code)
w.Close()

req, err := http.NewRequest("POST", "https://content.minetest.net/oauth/token/", &data)
if err != nil {
return "", err
}
req.Header.Set("Content-Type", w.FormDataContentType())

client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("unexpected status-code: %d", resp.StatusCode)
}

tokenData := AccessTokenResponse{}
err = json.NewDecoder(resp.Body).Decode(&tokenData)
if err != nil {
return "", err
}

return tokenData.AccessToken, nil
}

func (o *CDBOauth) RequestUserInfo(access_token string, cfg *OAuthConfig) (*OauthUserInfo, error) {
// fetch user data
req, err := http.NewRequest("GET", "https://content.minetest.net/api/whoami/", nil)
if err != nil {
return nil, err
}

req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+access_token)

client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

userData := CDBUserResponse{}
err = json.NewDecoder(resp.Body).Decode(&userData)
if err != nil {
return nil, err
}

info := OauthUserInfo{
Provider: ProviderTypeCDB,
Name: userData.Username,
ExternalID: userData.Username,
}

// fetch user profile
req, err = http.NewRequest("GET", fmt.Sprintf("https://content.minetest.net/api/users/%s/", userData.Username), nil)
if err != nil {
return nil, fmt.Errorf("new user-profile request error: %v", err)
}
req.Header.Set("Accept", "application/json")

resp, err = client.Do(req)
if err != nil {
return nil, fmt.Errorf("get user-profile error: %v", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unexpected status-code from user-profile api: %d", resp.StatusCode)
}
defer resp.Body.Close()

userProfile := CDBUser{}
err = json.NewDecoder(resp.Body).Decode(&userProfile)
if err != nil {
return nil, fmt.Errorf("user-profile response error: %v", err)
}

if userProfile.ProfilePicURL != "" {
info.AvatarURL = fmt.Sprintf("https://content.minetest.net%s", userProfile.ProfilePicURL)
}
if userProfile.DisplayName != "" {
info.DisplayName = userProfile.DisplayName
}

return &info, nil
}
96 changes: 96 additions & 0 deletions discord.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package oauth

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
)

type DiscordResponse struct {
ID string `json:"id"`
Username string `json:"username"`
AvatarHash string `json:"avatar"`
GlobalName string `json:"global_name"`
}

type DiscordOauth struct{}

func (o *DiscordOauth) LoginURL(cfg *OAuthConfig) string {
return fmt.Sprintf("https://discord.com/api/oauth2/authorize?client_id=%s&redirect_uri=%s&response_type=code&scope=identify", cfg.ClientID, url.QueryEscape(cfg.CallbackURL))
}

func (o *DiscordOauth) RequestAccessToken(code string, cfg *OAuthConfig) (string, error) {
q := url.Values{}
q.Add("client_id", cfg.ClientID)
q.Add("client_secret", cfg.Secret)
q.Add("redirect_uri", cfg.CallbackURL)
q.Add("code", code)
q.Add("grant_type", "authorization_code")
q.Add("scope", "identify connections")

buf := bytes.NewBufferString(q.Encode())

req, err := http.NewRequest("POST", "https://discord.com/api/oauth2/token", buf)
if err != nil {
return "", err
}

req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("invalid status code in token-response: %d", resp.StatusCode)
}
defer resp.Body.Close()

tokenData := AccessTokenResponse{}
err = json.NewDecoder(resp.Body).Decode(&tokenData)
if err != nil {
return "", err
}

return tokenData.AccessToken, nil
}

func (o *DiscordOauth) RequestUserInfo(access_token string, cfg *OAuthConfig) (*OauthUserInfo, error) {
req, err := http.NewRequest("GET", "https://discord.com/api/users/@me", nil)
if err != nil {
return nil, nil
}

req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+access_token)

client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("invalid status code in response: %d", resp.StatusCode)
}

userData := DiscordResponse{}
err = json.NewDecoder(resp.Body).Decode(&userData)
if err != nil {
return nil, err
}

info := OauthUserInfo{
Provider: ProviderTypeDiscord,
Name: userData.Username,
ExternalID: userData.ID,
DisplayName: userData.GlobalName,
AvatarURL: fmt.Sprintf("https://cdn.discordapp.com/avatars/%s/%s.png", userData.ID, userData.AvatarHash),
}

return &info, nil
}
95 changes: 95 additions & 0 deletions github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package oauth

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strconv"
)

type GithubAccessTokenRequest struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Code string `json:"code"`
}

type GithubUserResponse struct {
ID int `json:"id"`
Login string `json:"login"`
}

type GithubOauth struct{}

func (o *GithubOauth) LoginURL(cfg *OAuthConfig) string {
return fmt.Sprintf("https://github.com/login/oauth/authorize?client_id=%s", cfg.ClientID)
}

func (o *GithubOauth) RequestAccessToken(code string, cfg *OAuthConfig) (string, error) {
accessTokenReq := GithubAccessTokenRequest{
ClientID: cfg.ClientID,
ClientSecret: cfg.Secret,
Code: code,
}

data, err := json.Marshal(accessTokenReq)
if err != nil {
return "", err
}

req, err := http.NewRequest("POST", "https://github.com/login/oauth/access_token", bytes.NewBuffer(data))
if err != nil {
return "", err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")

client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()

tokenData := AccessTokenResponse{}
err = json.NewDecoder(resp.Body).Decode(&tokenData)
if err != nil {
return "", err
}

return tokenData.AccessToken, nil
}

func (o *GithubOauth) RequestUserInfo(access_token string, cfg *OAuthConfig) (*OauthUserInfo, error) {
// fetch user data
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
if err != nil {
return nil, err
}

req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+access_token)

client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

userData := GithubUserResponse{}
err = json.NewDecoder(resp.Body).Decode(&userData)
if err != nil {
return nil, err
}

external_id := strconv.Itoa(userData.ID)
info := OauthUserInfo{
Provider: ProviderTypeGithub,
Name: userData.Login,
ExternalID: external_id,
AvatarURL: fmt.Sprintf("https://github.com/%s.png", userData.Login),
}

return &info, nil
}
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/minetest-go/oauth

go 1.21.5

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading

0 comments on commit 51df390

Please sign in to comment.