all repos

onasty @ c672974

a one-time notes service

onasty/internal/transport/http/apiv1/auth.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
refactor: endpoints should not be pointer receiver (#217), 8 months ago
1
package apiv1
2
3
import (
4
	"net/http"
5
	"net/url"
6
	"time"
7
8
	"github.com/gin-gonic/gin"
9
	"github.com/olexsmir/onasty/internal/dtos"
10
)
11
12
type signUpRequest struct {
13
	Email    string `json:"email"`
14
	Password string `json:"password"`
15
}
16
17
func (a APIV1) signUpHandler(c *gin.Context) {
18
	var req signUpRequest
19
	if err := c.ShouldBindJSON(&req); err != nil {
20
		invalidRequest(c)
21
		return
22
	}
23
24
	if err := a.authsrv.SignUp(c.Request.Context(), dtos.SignUp{
25
		Email:       req.Email,
26
		Password:    req.Password,
27
		CreatedAt:   time.Now(),
28
		LastLoginAt: time.Now(),
29
	}); err != nil {
30
		errorResponse(c, err)
31
		return
32
	}
33
34
	c.Status(http.StatusCreated)
35
}
36
37
type signInRequest struct {
38
	Email    string `json:"email"`
39
	Password string `json:"password"`
40
}
41
42
type signInResponse struct {
43
	AccessToken  string `json:"access_token"`
44
	RefreshToken string `json:"refresh_token"`
45
}
46
47
func (a APIV1) signInHandler(c *gin.Context) {
48
	var req signInRequest
49
	if err := c.ShouldBindJSON(&req); err != nil {
50
		invalidRequest(c)
51
		return
52
	}
53
54
	toks, err := a.authsrv.SignIn(c.Request.Context(), dtos.SignIn{
55
		Email:    req.Email,
56
		Password: req.Password,
57
	})
58
	if err != nil {
59
		errorResponse(c, err)
60
		return
61
	}
62
63
	c.JSON(http.StatusOK, signInResponse{
64
		AccessToken:  toks.Access,
65
		RefreshToken: toks.Refresh,
66
	})
67
}
68
69
type refreshTokenRequest struct {
70
	RefreshToken string `json:"refresh_token"`
71
}
72
73
func (a APIV1) refreshTokensHandler(c *gin.Context) {
74
	var req refreshTokenRequest
75
	if err := c.ShouldBindJSON(&req); err != nil {
76
		invalidRequest(c)
77
		return
78
	}
79
80
	toks, err := a.authsrv.RefreshTokens(c.Request.Context(), req.RefreshToken)
81
	if err != nil {
82
		errorResponse(c, err)
83
		return
84
	}
85
86
	c.JSON(http.StatusOK, signInResponse{
87
		AccessToken:  toks.Access,
88
		RefreshToken: toks.Refresh,
89
	})
90
}
91
92
type logoutRequest struct {
93
	RefreshToken string `json:"refresh_token"`
94
}
95
96
func (a APIV1) logOutHandler(c *gin.Context) {
97
	var req logoutRequest
98
	if err := c.ShouldBindJSON(&req); err != nil {
99
		invalidRequest(c)
100
		return
101
	}
102
103
	if err := a.authsrv.Logout(
104
		c.Request.Context(),
105
		a.getUserID(c),
106
		req.RefreshToken,
107
	); err != nil {
108
		errorResponse(c, err)
109
		return
110
	}
111
112
	c.Status(http.StatusNoContent)
113
}
114
115
func (a APIV1) logOutAllHandler(c *gin.Context) {
116
	if err := a.authsrv.LogoutAll(c.Request.Context(), a.getUserID(c)); err != nil {
117
		errorResponse(c, err)
118
		return
119
	}
120
121
	c.Status(http.StatusNoContent)
122
}
123
124
const oatuhStateCookie = "oauth_state"
125
126
func (a APIV1) oauthLoginHandler(c *gin.Context) {
127
	redirectInfo, err := a.authsrv.GetOAuthURL(c.Param("provider"))
128
	if err != nil {
129
		errorResponse(c, err)
130
		return
131
	}
132
133
	c.SetCookie(
134
		oatuhStateCookie,
135
		redirectInfo.State,
136
		int(time.Minute.Seconds()),
137
		"/",
138
		a.appURL,
139
		!a.env.IsDevMode(),
140
		true,
141
	)
142
143
	c.Redirect(http.StatusSeeOther, redirectInfo.URL)
144
}
145
146
func (a APIV1) oauthCallbackHandler(c *gin.Context) {
147
	redURL, err := url.Parse(a.frontendURL + "/oauth/callback")
148
	if err != nil {
149
		errorResponse(c, err)
150
		return
151
	}
152
153
	storedState, err := c.Cookie(oatuhStateCookie)
154
	if err != nil || c.Query("state") != storedState {
155
		a.oauthCallbackErrorResponse(c, redURL)
156
		return
157
	}
158
159
	tokens, err := a.authsrv.HandleOAuthLogin(
160
		c.Request.Context(),
161
		c.Param("provider"),
162
		c.Query("code"),
163
	)
164
	if err != nil {
165
		a.oauthCallbackErrorResponse(c, redURL)
166
		return
167
	}
168
169
	redURL.RawQuery = url.Values{
170
		"access_token":  {tokens.Access},
171
		"refresh_token": {tokens.Refresh},
172
	}.Encode()
173
174
	c.Redirect(http.StatusFound, redURL.String())
175
}
176
177
func (a APIV1) oauthCallbackErrorResponse(c *gin.Context, u *url.URL) {
178
	u.RawQuery = url.Values{"error": {"internal server error"}}.Encode()
179
	c.Redirect(http.StatusFound, u.String())
180
}