10
"github.com/go-chi/chi/v5"
12
"xelbot.com/reprogl/api/backend"
13
"xelbot.com/reprogl/container"
14
"xelbot.com/reprogl/models/repositories"
15
"xelbot.com/reprogl/services/oauth"
16
"xelbot.com/reprogl/session"
17
"xelbot.com/reprogl/views"
20
type oauthCallbackState struct {
21
Status string `json:"status"`
22
UserName string `json:"username,omitempty"`
23
NickName string `json:"nickname,omitempty"`
26
type oauthStateResponse struct {
27
Status string `json:"status"`
28
RedirectURL string `json:"redirect_url,omitempty"`
31
func OAuthLogin(app *container.Application) http.HandlerFunc {
32
return func(w http.ResponseWriter, r *http.Request) {
33
providerName := chi.URLParam(r, "provider")
35
app.InfoLog.Println("[OAUTH] start authorization by: " + providerName)
36
oauthConfig, err := oauth.ConfigByProvider(providerName)
43
saveLoginReferer(w, r)
45
state := generateRandomToken()
46
session.Put(r.Context(), session.OAuthStateKey, state)
48
verifier := oauth2.GenerateVerifier()
49
session.Put(r.Context(), session.OAuthVerifierKey, verifier)
51
redirectURL := oauthConfig.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier))
52
app.InfoLog.Println("[OAUTH] redirect to: " + redirectURL)
54
http.Redirect(w, r, redirectURL, http.StatusFound)
58
func OAuthCallback(app *container.Application) http.HandlerFunc {
59
return func(w http.ResponseWriter, r *http.Request) {
60
providerName := chi.URLParam(r, "provider")
62
app.InfoLog.Println("[OAUTH] callback from: " + providerName)
63
if !oauth.SupportedProvider(providerName) {
69
state, _ := session.Pop[string](r.Context(), session.OAuthStateKey)
70
stateFromRequest := r.FormValue("state")
72
if len(state) == 0 || len(stateFromRequest) == 0 || stateFromRequest != state {
73
app.InfoLog.Println("[OAUTH] Invalid state")
74
app.ClientError(w, http.StatusBadRequest)
80
verifier, found := session.Pop[string](r.Context(), session.OAuthVerifierKey)
82
app.ServerError(w, errors.New("[OAUTH] PKCE verifier not found"))
87
code := r.FormValue("code")
89
errorCode := r.FormValue("error")
90
errorDescription := r.FormValue("error_description")
91
if len(errorCode) > 0 {
92
app.InfoLog.Printf("[OAUTH] Error code: %s, description: %s\n", errorCode, errorDescription)
94
app.InfoLog.Println("[OAUTH] Error: empty code")
96
app.ClientError(w, http.StatusBadRequest)
101
additional := make(map[string]string)
102
for _, key := range oauth.AdditionalParams(providerName) {
103
additional[key] = r.FormValue(key)
106
requestID := generateRandomToken()
107
go asyncCallback(requestID, providerName, code, verifier, r.UserAgent(), container.RealRemoteAddress(r), additional, app)
109
templateData := views.NewOauthPendingPageData(requestID)
110
err := views.WriteTemplate(w, "oauth-pending.gohtml", templateData)
112
app.ServerError(w, err)
126
additional map[string]string,
127
app *container.Application,
129
cache := app.GetStringCache()
130
cache.Set(requestID, `{"status":"pending"}`, time.Minute)
132
userData, err := oauth.UserDataByCode(providerName, code, verifier, additional)
134
oauthCallbackError(app, requestID, err)
139
userDataDTO := backend.ExternalUserDTO{
141
UserAgent: userAgent,
145
apiResponse, err := backend.SendUserData(userDataDTO)
147
oauthCallbackError(app, requestID, err)
152
if apiResponse.Violations != nil && len(apiResponse.Violations) > 0 {
153
errorMessage := "[OAUTH] user validation error:\n"
154
for _, formError := range apiResponse.Violations {
155
app.InfoLog.Printf("[OAUTH] validation error: %s - %s\n", formError.Path, formError.Message)
156
errorMessage += fmt.Sprintf("%s: %s\n", formError.Path, formError.Message)
159
oauthCallbackError(app, requestID, err)
164
if apiResponse.User != nil {
165
oauthState := oauthCallbackState{
167
UserName: apiResponse.User.Username,
168
NickName: apiResponse.User.Nickname(),
171
jsonBody, err := json.Marshal(oauthState)
173
oauthCallbackError(app, requestID, err)
177
cache.Set(requestID, string(jsonBody), time.Minute)
181
func OAuthCheckState(app *container.Application) http.HandlerFunc {
182
return func(w http.ResponseWriter, r *http.Request) {
183
requestID := chi.URLParam(r, "request_id")
185
var stateString string
188
cache := app.GetStringCache()
189
if stateString, found = cache.Get(requestID); !found {
190
app.InfoLog.Println("[OAUTH] requestID not found: " + requestID)
196
buf := []byte(stateString)
197
if !json.Valid(buf) {
198
app.ServerError(w, errors.New("[OAUTH] invalid JSON state"))
203
var oauthState oauthCallbackState
204
err := json.Unmarshal(buf, &oauthState)
206
app.ServerError(w, err)
211
responseData := oauthStateResponse{
212
Status: oauthState.Status,
215
if oauthState.Status == "ok" && len(oauthState.UserName) > 0 {
216
session.Put(r.Context(), session.FlashSuccessKey, fmt.Sprintf("Привет, %s :)", oauthState.NickName))
218
repo := repositories.UserRepository{DB: app.DB}
219
user, err := repo.GetLoggedUserByUsername(oauthState.UserName)
221
app.ServerError(w, err)
226
app.InfoLog.Printf("[OAUTH] success for \"%s\"\n", user.Username)
227
authSuccess(user, app, container.RealRemoteAddress(r), r.Context())
229
var redirectUrl string
230
if redirectUrl, found = popLoginReferer(w, r); !found {
234
responseData.RedirectURL = redirectUrl
237
jsonResponse(w, http.StatusOK, responseData)
241
func oauthCallbackError(app *container.Application, requestID string, err error) {
244
cache := app.GetStringCache()
245
cache.Set(requestID, `{"status":"error"}`, time.Minute)