Completed
Push — main ( 3a80b2...74ebe2 )
by Yume
15s queued 13s
created

controllers.*OAuthController.DiscordCallback   C

Complexity

Conditions 9

Size

Total Lines 57
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 35
nop 1
dl 0
loc 57
rs 6.6666
c 0
b 0
f 0

How to fix   Long Method   

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:

1
package controllers
2
3
import (
4
	"fmt"
5
6
	"github.com/gofiber/fiber/v2"
7
	"github.com/memnix/memnix-rest/config"
8
	"github.com/memnix/memnix-rest/domain"
9
	"github.com/memnix/memnix-rest/infrastructures"
10
	"github.com/memnix/memnix-rest/internal/auth"
11
	"github.com/memnix/memnix-rest/pkg/oauth"
12
	"github.com/memnix/memnix-rest/pkg/random"
13
	"github.com/memnix/memnix-rest/views"
14
	"github.com/uptrace/opentelemetry-go-extra/otelzap"
15
	"go.opentelemetry.io/otel/attribute"
16
	"go.uber.org/zap"
17
)
18
19
// OAuthController is the controller for the OAuth routes
20
type OAuthController struct {
21
	auth auth.IUseCase // auth usecase
22
	auth.IAuthRedisRepository
23
}
24
25
// NewOAuthController creates a new OAuthController
26
func NewOAuthController(auth auth.IUseCase, redisRepository auth.IAuthRedisRepository) OAuthController {
27
	return OAuthController{auth: auth, IAuthRedisRepository: redisRepository}
28
}
29
30
// GithubLogin redirects the user to the github login page
31
//
32
//	@Summary		Redirects the user to the github login page
33
//	@Description	Redirects the user to the github login page
34
//	@Tags			OAuth
35
//	@Accept			json
36
//	@Produce		json
37
//	@Success		302	{string}	string					"redirecting to github login"
38
//	@Failure		500	{object}	views.HTTPResponseVM	"internal server error"
39
//	@Router			/v2/security/github [get]
40
func (a *OAuthController) GithubLogin(c *fiber.Ctx) error {
41
	state, _ := random.GenerateSecretCode(config.OauthStateLength)
42
	// Create the dynamic redirect URL for login
43
	redirectURL := fmt.Sprintf(
44
		"https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&state=%s",
45
		infrastructures.AppConfig.GithubConfig.ClientID,
46
		config.GetCurrentURL()+"/v2/security/github_callback",
47
		state,
48
	)
49
	// Save the state in the cache
50
	if err := a.IAuthRedisRepository.SetState(c.UserContext(), state); err != nil {
51
		return err
52
	}
53
	if err := c.Redirect(redirectURL, fiber.StatusSeeOther); err != nil {
54
		return err
55
	}
56
	return c.JSON(fiber.Map{"message": "redirecting to github login", "redirect_url": redirectURL})
57
}
58
59
// GithubCallback handles the callback from github
60
//
61
//	@Summary		Handles the callback from github
62
//	@Description	Handles the callback from github
63
//	@Tags			OAuth
64
//	@Accept			json
65
//	@Produce		json
66
//	@Param			code	query		string					true	"code from github"
67
//	@Success		200		{object}	views.LoginTokenVM		"login token"
68
//	@Failure		401		{object}	views.HTTPResponseVM	"invalid credentials"
69
//	@Failure		500		{object}	views.HTTPResponseVM	"internal server error"
70
//	@Router			/v2/security/github_callback [get]
71
func (a *OAuthController) GithubCallback(c *fiber.Ctx) error {
72
	// get the code from the query string
73
	code := c.Query("code")
74
	state := c.Query("state")
75
76
	// check if the state is valid
77
	if ok, _ := a.IAuthRedisRepository.HasState(c.UserContext(), state); !ok {
78
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
79
	}
80
81
	// get the access token from github
82
	accessToken, err := oauth.GetGithubAccessToken(c.UserContext(), code)
83
	if err != nil {
84
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
85
	}
86
87
	// get the user from github
88
	user, err := oauth.GetGithubData(c.UserContext(), accessToken)
89
	if err != nil {
90
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
91
	}
92
93
	var githubUser domain.GithubLogin
94
	err = config.JSONHelper.Unmarshal([]byte(user), &githubUser)
95
	if err != nil {
96
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
97
	}
98
99
	// log the user
100
	jwtToken, err := a.auth.LoginOauth(c.UserContext(), githubUser.ToUser())
101
	if err != nil {
102
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
103
	}
104
105
	// Delete the state from the cache
106
	if err = a.IAuthRedisRepository.DeleteState(c.UserContext(), state); err != nil {
107
		otelzap.Ctx(c.UserContext()).Error("failed to delete state from cache", zap.Error(err))
108
	}
109
110
	return c.Redirect(config.GetFrontURL()+"/callback/"+jwtToken, fiber.StatusSeeOther)
111
}
112
113
// DiscordLogin redirects the user to the discord login page
114
//
115
//	@Summary		Redirects the user to the discord login page
116
//	@Description	Redirects the user to the discord login page
117
//	@Tags			OAuth
118
//	@Accept			json
119
//	@Produce		json
120
//	@Success		302	{string}	string					"redirecting to github login"
121
//	@Failure		500	{object}	views.HTTPResponseVM	"internal server error"
122
//	@Router			/v2/security/discord [get]
123
func (a *OAuthController) DiscordLogin(c *fiber.Ctx) error {
124
	// Create the dynamic redirect URL for login
125
	state, _ := random.GenerateSecretCode(config.OauthStateLength)
126
	if err := a.IAuthRedisRepository.SetState(c.UserContext(), state); err != nil {
127
		return err
128
	}
129
130
	redirectURL := infrastructures.AppConfig.DiscordConfig.URL + "&state=" + state
131
132
	err := c.Redirect(redirectURL, fiber.StatusSeeOther)
133
	if err != nil {
134
		return err
135
	}
136
	return c.JSON(fiber.Map{"message": "redirecting to discord login", "redirect_url": redirectURL})
137
}
138
139
// DiscordCallback handles the callback from discord
140
//
141
//	@Summary		Handles the callback from discord
142
//	@Description	Handles the callback from discord
143
//	@Tags			OAuth
144
//	@Accept			json
145
//	@Produce		json
146
//	@Param			code	query		string					true	"code from discord"
147
//	@Success		200		{object}	views.LoginTokenVM		"login token"
148
//	@Failure		401		{object}	views.HTTPResponseVM	"invalid credentials"
149
//	@Failure		500		{object}	views.HTTPResponseVM	"internal server error"
150
//	@Router			/v2/security/discord_callback [get]
151
func (a *OAuthController) DiscordCallback(c *fiber.Ctx) error {
152
	_, span := infrastructures.GetFiberTracer().Start(c.UserContext(), "DiscordCallback")
153
	defer span.End()
154
	// get the code from the query string
155
	code := c.Query("code")
156
	state := c.Query("state")
157
158
	span.SetAttributes(attribute.String("code", code), attribute.String("state", state))
159
	if ok, _ := a.IAuthRedisRepository.HasState(c.UserContext(), state); !ok {
160
		otelzap.Ctx(c.UserContext()).Warn("state not found", zap.String("state", state))
161
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
162
	}
163
164
	// get the access token from discord
165
	accessToken, err := oauth.GetDiscordAccessToken(c.UserContext(), code)
166
	if err != nil {
167
		otelzap.Ctx(c.UserContext()).Error("failed to get access token from discord", zap.Error(err))
168
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
169
	}
170
171
	if accessToken == "" {
172
		otelzap.Ctx(c.UserContext()).Error("access token is empty")
173
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
174
	}
175
176
	// get the user from discord
177
	user, err := oauth.GetDiscordData(c.UserContext(), accessToken)
178
	if err != nil {
179
		otelzap.Ctx(c.UserContext()).Error("failed to get user from discord", zap.Error(err))
180
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
181
	}
182
183
	var discordUser domain.DiscordLogin
184
	// print the user to the console
185
	err = config.JSONHelper.Unmarshal([]byte(user), &discordUser)
186
	if err != nil {
187
		otelzap.Ctx(c.UserContext()).Error("failed to unmarshal discord user", zap.Error(err))
188
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
189
	}
190
191
	if discordUser == (domain.DiscordLogin{}) {
192
		otelzap.Ctx(c.UserContext()).Error("discord user is empty")
193
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
194
	}
195
196
	// log the user
197
	jwtToken, err := a.auth.LoginOauth(c.UserContext(), discordUser.ToUser())
198
	if err != nil {
199
		otelzap.Ctx(c.UserContext()).Error("failed to login user", zap.Error(err))
200
		return c.Status(fiber.StatusUnauthorized).JSON(views.NewLoginTokenVM("", views.InvalidCredentials))
201
	}
202
203
	if err = a.IAuthRedisRepository.DeleteState(c.UserContext(), state); err != nil {
204
		otelzap.Ctx(c.UserContext()).Error("error deleting state", zap.Error(err))
205
	}
206
207
	return c.Redirect(config.GetFrontURL()+"/callback/"+jwtToken, fiber.StatusSeeOther)
208
}
209