@@ -3,6 +3,8 @@ package controllers
33import (
44 "ccsync_backend/utils"
55 "context"
6+ "crypto/rand"
7+ "encoding/base64"
68 "encoding/json"
79 "net/http"
810 "os"
@@ -11,6 +13,15 @@ import (
1113 "golang.org/x/oauth2"
1214)
1315
16+ // generateOAuthState creates a cryptographically secure random state string
17+ func generateOAuthState () (string , error ) {
18+ b := make ([]byte , 32 )
19+ if _ , err := rand .Read (b ); err != nil {
20+ return "" , err
21+ }
22+ return base64 .URLEncoding .EncodeToString (b ), nil
23+ }
24+
1425type App struct {
1526 Config * oauth2.Config
1627 SessionStore * sessions.CookieStore
@@ -26,9 +37,27 @@ type App struct {
2637// @Accept json
2738// @Produce json
2839// @Success 307 {string} string "Redirect to OAuth provider"
40+ // @Failure 500 {string} string "Internal server error"
2941// @Router /auth/oauth [get]
3042func (a * App ) OAuthHandler (w http.ResponseWriter , r * http.Request ) {
31- url := a .Config .AuthCodeURL ("state" , oauth2 .AccessTypeOffline )
43+ // Generate a cryptographically secure random state to prevent CSRF attacks
44+ state , err := generateOAuthState ()
45+ if err != nil {
46+ utils .Logger .Errorf ("Failed to generate OAuth state: %v" , err )
47+ http .Error (w , "Internal server error" , http .StatusInternalServerError )
48+ return
49+ }
50+
51+ // Store state in session for validation in callback
52+ session , _ := a .SessionStore .Get (r , "session-name" )
53+ session .Values ["oauth_state" ] = state
54+ if err := session .Save (r , w ); err != nil {
55+ utils .Logger .Errorf ("Failed to save OAuth state to session: %v" , err )
56+ http .Error (w , "Internal server error" , http .StatusInternalServerError )
57+ return
58+ }
59+
60+ url := a .Config .AuthCodeURL (state , oauth2 .AccessTypeOffline )
3261 http .Redirect (w , r , url , http .StatusTemporaryRedirect )
3362}
3463
@@ -39,11 +68,25 @@ func (a *App) OAuthHandler(w http.ResponseWriter, r *http.Request) {
3968// @Accept json
4069// @Produce json
4170// @Param code query string true "OAuth authorization code"
71+ // @Param state query string true "OAuth state parameter for CSRF protection"
4272// @Success 303 {string} string "Redirect to frontend home page"
4373// @Failure 400 {string} string "Bad request"
74+ // @Failure 403 {string} string "Invalid OAuth state"
4475// @Failure 500 {string} string "Internal server error"
4576// @Router /auth/callback [get]
4677func (a * App ) OAuthCallbackHandler (w http.ResponseWriter , r * http.Request ) {
78+ // Validate OAuth state parameter to prevent CSRF attacks
79+ state := r .URL .Query ().Get ("state" )
80+ session , _ := a .SessionStore .Get (r , "session-name" )
81+ expectedState , ok := session .Values ["oauth_state" ].(string )
82+ if ! ok || state == "" || state != expectedState {
83+ utils .Logger .Warnf ("OAuth state mismatch: expected=%s, got=%s" , expectedState , state )
84+ http .Error (w , "Invalid OAuth state" , http .StatusForbidden )
85+ return
86+ }
87+ // Clear the state from session after validation (one-time use)
88+ delete (session .Values , "oauth_state" )
89+
4790 code := r .URL .Query ().Get ("code" )
4891
4992 t , err := a .Config .Exchange (context .Background (), code )
@@ -77,7 +120,6 @@ func (a *App) OAuthCallbackHandler(w http.ResponseWriter, r *http.Request) {
77120
78121 userInfo ["uuid" ] = uuidStr
79122 userInfo ["encryption_secret" ] = encryptionSecret
80- session , _ := a .SessionStore .Get (r , "session-name" )
81123 session .Values ["user" ] = userInfo
82124 if err := session .Save (r , w ); err != nil {
83125 http .Error (w , err .Error (), http .StatusInternalServerError )
0 commit comments