Passed
Push — main ( f6597a...58b626 )
by Yume
01:31 queued 12s
created

controllers.*OAuthController.DiscordCallback   B

Complexity

Conditions 8

Size

Total Lines 48
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

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