oauth.GetDiscordAccessToken   C
last analyzed

Complexity

Conditions 11

Size

Total Lines 58
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 36
dl 0
loc 58
rs 5.4
c 0
b 0
f 0
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like oauth.GetDiscordAccessToken often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package oauth
2
3
import (
4
	"bytes"
5
	"context"
6
	"fmt"
7
	"io"
8
	"log/slog"
9
	"net/http"
10
11
	"github.com/pkg/errors"
12
)
13
14
const (
15
	discordTokenURL = "https://discord.com/api/oauth2/token" //nolint:gosec //This is a URL, not a password.
16
	discordAPIURL   = "https://discord.com/api/users/@me"    //nolint:gosec //This is a URL, not a password.
17
)
18
19
// Represents the response received from Discord.
20
type discordAccessTokenResponse struct {
21
	AccessToken  string `json:"access_token"`
22
	TokenType    string `json:"token_type"`
23
	Scope        string `json:"scope"`
24
	RefreshToken string `json:"refresh_token"`
25
	Expires      int    `json:"expires_in"`
26
}
27
28
// GetDiscordAccessToken gets the access token from Discord.
29
func GetDiscordAccessToken(ctx context.Context, code string) (string, error) {
30
	reqBody := bytes.NewBufferString(fmt.Sprintf(
31
		"client_id=%s&client_secret=%s&grant_type=authorization_code&redirect_uri=%s&code=%s&scope=identify,email",
32
		discordConfig.ClientID,
33
		discordConfig.ClientSecret,
34
		GetCallbackURL()+"/v2/security/discord_callback",
35
		code,
36
	))
37
38
	// POST request to set URL.
39
	req, err := http.NewRequestWithContext(ctx,
40
		http.MethodPost,
41
		discordTokenURL,
42
		reqBody,
43
	)
44
	if err != nil {
45
		slog.Error("Failed to get Discord access token", slog.Any("error", err))
46
		return "", errors.Wrap(err, RequestFailed)
47
	}
48
49
	if req == nil || req.Body == nil || req.Header == nil {
50
		slog.Error("Failed to get Discord access token", slog.Any("error", err))
51
		return "", errors.New(RequestFailed)
52
	}
53
54
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
55
	req.Header.Set("Accept", "application/json")
56
57
	// Get the response.
58
	resp, resperr := http.DefaultClient.Do(req)
59
	if resperr != nil {
60
		slog.Error("Failed to get Discord access token", slog.Any("error", resperr))
61
		return "", errors.Wrap(resperr, ResponseFailed)
62
	}
63
64
	defer func(resp *http.Response) {
65
		if resp != nil && resp.Body != nil {
66
			resp.Body.Close()
67
		}
68
	}(resp)
69
70
	// Response body converted to stringified JSON.
71
	respbody, err := io.ReadAll(resp.Body)
72
	if err != nil {
73
		slog.Error("failed to read resp.body", slog.Any("error", err))
74
		return "", err
75
	}
76
77
	// Convert stringified JSON to a struct object of type githubAccessTokenResponse.
78
	var ghresp discordAccessTokenResponse
79
	err = GetJSONHelperInstance().GetJSONHelper().Unmarshal(respbody, &ghresp)
80
	if err != nil {
81
		return "", err
82
	}
83
84
	// Return the access token (as the rest of the
85
	// details are relatively unnecessary for us).
86
	return ghresp.AccessToken, nil
87
}
88
89
// GetDiscordData gets the user data from Discord.
90
func GetDiscordData(ctx context.Context, accessToken string) (string, error) {
91
	req, err := http.NewRequestWithContext(ctx,
92
		http.MethodGet,
93
		discordAPIURL,
94
		nil,
95
	)
96
	if err != nil {
97
		return "", errors.Wrap(err, RequestFailed)
98
	}
99
100
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
101
102
	// Get the response.
103
	resp, err := http.DefaultClient.Do(req)
104
	if err != nil {
105
		return "", errors.Wrap(err, ResponseFailed)
106
	}
107
108
	defer func(resp *http.Response) {
109
		if resp != nil && resp.Body != nil {
110
			resp.Body.Close()
111
		}
112
	}(resp)
113
	// Response body converted to stringified JSON.
114
	respbody, err := io.ReadAll(resp.Body)
115
	if err != nil {
116
		return "", err
117
	}
118
119
	return string(respbody), nil
120
}
121