Passed
Pull Request — main (#128)
by Yume
01:14
created

oauth.GetDiscordData   A

Complexity

Conditions 4

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 19
nop 2
dl 0
loc 32
rs 9.45
c 0
b 0
f 0
1
package oauth
2
3
import (
4
	"bytes"
5
	"context"
6
	"fmt"
7
	"io"
8
	"net/http"
9
10
	"github.com/memnix/memnix-rest/config"
11
	"github.com/memnix/memnix-rest/infrastructures"
12
	"github.com/memnix/memnix-rest/views"
13
	"github.com/pkg/errors"
14
	"github.com/uptrace/opentelemetry-go-extra/otelzap"
15
	"go.opentelemetry.io/otel/attribute"
16
	"go.opentelemetry.io/otel/trace"
17
	"go.uber.org/zap"
18
)
19
20
const (
21
	discordTokenURL = "https://discord.com/api/oauth2/token"
22
	discordAPIURL   = "https://discord.com/api/users/@me"
23
)
24
25
// Represents the response received from Discord
26
type discordAccessTokenResponse struct {
27
	AccessToken  string `json:"access_token"`
28
	TokenType    string `json:"token_type"`
29
	Scope        string `json:"scope"`
30
	Expires      int    `json:"expires_in"`
31
	RefreshToken string `json:"refresh_token"`
32
}
33
34
// GetDiscordAccessToken gets the access token from Discord
35
func GetDiscordAccessToken(ctx context.Context, code string) (string, error) {
36
	_, span := infrastructures.GetFiberTracer().Start(ctx, "GetDiscordAccessToken")
37
	defer span.End()
38
39
	reqBody := bytes.NewBuffer([]byte(fmt.Sprintf(
40
		"client_id=%s&client_secret=%s&grant_type=authorization_code&redirect_uri=%s&code=%s&scope=identify,email",
41
		infrastructures.AppConfig.DiscordConfig.ClientID,
42
		infrastructures.AppConfig.DiscordConfig.ClientSecret,
43
		config.GetCurrentURL()+"/v2/security/discord_callback",
44
		code,
45
	)))
46
47
	// POST request to set URL
48
	req, err := http.NewRequestWithContext(ctx,
49
		http.MethodPost,
50
		discordTokenURL,
51
		reqBody,
52
	)
53
	if err != nil {
54
		otelzap.Ctx(ctx).Error("Failed to get Discord access token", zap.Error(err))
55
		return "", errors.Wrap(err, views.RequestFailed)
56
	}
57
58
	if req == nil || req.Body == nil || req.Header == nil {
59
		otelzap.Ctx(ctx).Error("Failed to get Discord access token", zap.Error(err))
60
		return "", errors.New(views.RequestFailed)
61
	}
62
63
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
64
	req.Header.Set("Accept", "application/json")
65
66
	// Get the response
67
	resp, resperr := http.DefaultClient.Do(req)
68
	if resperr != nil {
69
		otelzap.Ctx(ctx).Error("Failed to get Discord access token", zap.Error(resperr))
70
		return "", errors.Wrap(resperr, views.ResponseFailed)
71
	}
72
73
	defer func(resp *http.Response) {
74
		if resp != nil && resp.Body != nil {
75
			resp.Body.Close()
76
		}
77
	}(resp)
78
79
	// Response body converted to stringified JSON
80
	respbody, err := io.ReadAll(resp.Body)
81
	if err != nil {
82
		otelzap.Ctx(ctx).Error("failed to read resp.body", zap.Error(err))
83
		return "", err
84
	}
85
86
	// Convert stringified JSON to a struct object of type githubAccessTokenResponse
87
	var ghresp discordAccessTokenResponse
88
	err = config.JSONHelper.Unmarshal(respbody, &ghresp)
89
	if err != nil {
90
		return "", err
91
	}
92
93
	span.AddEvent("Discord access token received", trace.WithAttributes(attribute.String("access_token", ghresp.AccessToken)))
94
95
	// Return the access token (as the rest of the
96
	// details are relatively unnecessary for us)
97
	return ghresp.AccessToken, nil
98
}
99
100
// GetDiscordData gets the user data from Discord
101
func GetDiscordData(ctx context.Context, accessToken string) (string, error) {
102
	_, span := infrastructures.GetFiberTracer().Start(ctx, "GetDiscordData")
103
	defer span.End()
104
105
	req, err := http.NewRequestWithContext(ctx,
106
		http.MethodGet,
107
		discordAPIURL,
108
		nil,
109
	)
110
	if err != nil {
111
		return "", errors.Wrap(err, views.RequestFailed)
112
	}
113
114
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
115
116
	// Get the response
117
	resp, err := http.DefaultClient.Do(req)
118
	if err != nil {
119
		return "", errors.Wrap(err, views.ResponseFailed)
120
	}
121
122
	defer resp.Body.Close()
123
124
	// Response body converted to stringified JSON
125
	respbody, err := io.ReadAll(resp.Body)
126
	if err != nil {
127
		return "", err
128
	}
129
130
	span.AddEvent("Discord user data received", trace.WithAttributes(attribute.String("user_data", string(respbody))))
131
132
	return string(respbody), nil
133
}
134