@@ -5,12 +5,14 @@ import (
55 "crypto/md5"
66 "encoding/base64"
77 "encoding/json"
8+ "errors"
89 "fmt"
910 "net/http"
1011 "net/url"
1112 "strconv"
1213 "time"
1314
15+ httpHelpers "github.com/canonical/identity-platform-login-ui/internal/misc/http"
1416 "github.com/go-chi/chi/v5"
1517 client "github.com/ory/kratos-client-go/v25"
1618
@@ -25,6 +27,7 @@ const RegenerateBackupCodesError = "regenerate_backup_codes"
2527const SESSION_REFRESH_REQUIRED = "session_refresh_required"
2628const KRATOS_SESSION_COOKIE_NAME = "ory_kratos_session"
2729const LOGIN_UI_STATE_COOKIE = "login_ui_state"
30+ const SECURITY_CSRF_VIOLATION_ERROR = "security_csrf_violation"
2831
2932type API struct {
3033 mfaEnabled bool
@@ -38,6 +41,12 @@ type API struct {
3841 logger logging.LoggerInterface
3942}
4043
44+ type KratosErrorResponse struct {
45+ Error * client.GenericError `json:"error,omitempty"`
46+ RedirectBrowserTo string `json:"redirect_browser_to,omitempty"`
47+ RedirectTo string `json:"redirect_to,omitempty"`
48+ }
49+
4150func (a * API ) RegisterEndpoints (mux * chi.Mux ) {
4251 mux .Post ("/api/kratos/self-service/login" , a .handleUpdateFlow )
4352 mux .Post ("/api/kratos/self-service/login/id-first" , a .handleUpdateIdentifierFirstFlow )
@@ -138,10 +147,17 @@ func (a *API) handleCreateFlow(w http.ResponseWriter, r *http.Request) {
138147 a .cookieManager .ClearStateCookie (w )
139148 }
140149 } else {
141- response , cookies , err = a .handleCreateFlowNewSession (r , aal , returnTo , loginChallenge , refresh )
150+ response , cookies , err = a .handleCreateFlowNewSession (r , aal , returnTo , loginChallenge , refresh , session )
142151 }
143152
144153 if err != nil {
154+ // Propagate the KratosErrorResponse so frontend can handle it
155+ if kratosError , ok := parseGenericError (err ); ok {
156+ w .WriteHeader (http .StatusBadRequest )
157+ _ = json .NewEncoder (w ).Encode (kratosError )
158+ return
159+ }
160+
145161 a .logger .Errorf (err .Error ())
146162 http .Error (w , err .Error (), http .StatusInternalServerError )
147163 return
@@ -167,15 +183,17 @@ func (a *API) handleCreateFlow(w http.ResponseWriter, r *http.Request) {
167183 _ = json .NewEncoder (w ).Encode (response )
168184}
169185
170- func (a * API ) handleCreateFlowNewSession (r * http.Request , aal , returnTo , loginChallenge string , refresh bool ) (* client.LoginFlow , []* http.Cookie , error ) {
186+ func (a * API ) handleCreateFlowNewSession (r * http.Request , aal , returnTo , loginChallenge string , refresh bool , session * client. Session ) (* client.LoginFlow , []* http.Cookie , error ) {
171187 // redirect user to this endpoint with the login_challenge after login
172188 // see https://github.com/ory/kratos/issues/3052
173189
174190 cookies := r .Cookies ()
175191
176- // clear cookies if not aal2 and not a refresh request
177- if ! refresh && aal != "aal2" {
178- cookies = filterCookies (cookies , KRATOS_SESSION_COOKIE_NAME )
192+ // clear cookies if not a refresh request, not aal2, and either:
193+ // - it's a hydra login (with loginChallenge)
194+ // - it's a kratos local login without a session
195+ if ! refresh && aal != "aal2" && (session == nil || loginChallenge != "" ) {
196+ cookies = httpHelpers .FilterCookies (cookies , KRATOS_SESSION_COOKIE_NAME )
179197 }
180198
181199 flow , cookies , err := a .service .CreateBrowserLoginFlow (
@@ -187,7 +205,7 @@ func (a *API) handleCreateFlowNewSession(r *http.Request, aal, returnTo, loginCh
187205 cookies ,
188206 )
189207 if err != nil {
190- return nil , nil , fmt .Errorf ("failed to create login flow, err: %v " , err )
208+ return nil , nil , fmt .Errorf ("failed to create login flow, err: %w " , err )
191209 }
192210
193211 flow , err = a .service .FilterFlowProviderList (r .Context (), flow )
@@ -201,12 +219,26 @@ func (a *API) handleCreateFlowNewSession(r *http.Request, aal, returnTo, loginCh
201219func (a * API ) handleCreateFlowWithSession (r * http.Request , session * client.Session , loginChallenge string ) (* BrowserLocationChangeRequired , []* http.Cookie , error ) {
202220 response , cookies , err := a .service .AcceptLoginRequest (r .Context (), session , loginChallenge )
203221 if err != nil {
204- return nil , nil , fmt .Errorf ("failed to accept login request: %v " , err )
222+ return nil , nil , fmt .Errorf ("failed to accept login request: %w " , err )
205223 }
206224
207225 return response , cookies , nil
208226}
209227
228+ func parseGenericError (err error ) (* KratosErrorResponse , bool ) {
229+ var apiErr * client.GenericOpenAPIError
230+ if ! errors .As (err , & apiErr ) {
231+ return nil , false
232+ }
233+
234+ var resp KratosErrorResponse
235+ if err := json .Unmarshal (apiErr .Body (), & resp ); err != nil {
236+ return nil , false
237+ }
238+
239+ return & resp , true
240+ }
241+
210242func (a * API ) returnToUrl (loginChallenge string ) (string , error ) {
211243 returnTo , err := url .JoinPath (a .baseURL , "/ui/login" )
212244 if err != nil {
@@ -244,6 +276,15 @@ func (a *API) handleGetLoginFlow(w http.ResponseWriter, r *http.Request) {
244276
245277 flow , cookies , err := a .service .GetLoginFlow (r .Context (), flowId , r .Cookies ())
246278 if err != nil {
279+ if kratosError , ok := parseGenericError (err ); ok {
280+ if kratosError .Error .GetId () == SECURITY_CSRF_VIOLATION_ERROR {
281+ a .deleteKratosSession (w )
282+ }
283+ w .WriteHeader (http .StatusBadRequest )
284+ _ = json .NewEncoder (w ).Encode (kratosError )
285+ return
286+ }
287+
247288 a .logger .Errorf ("Error when getting login flow: %v\n " , err )
248289 http .Error (w , "Failed to get login flow" , http .StatusInternalServerError )
249290 return
@@ -298,12 +339,12 @@ func (a *API) handleGetRegistrationFlow(w http.ResponseWriter, r *http.Request)
298339func (a * API ) handleUpdateRegistrationFlow (w http.ResponseWriter , r * http.Request ) {
299340 q := r .URL .Query ()
300341 flowId := q .Get ("flow" )
301- if flowId == "" {
302- a .logger .Errorf ("ID parameter not present" )
303- w .WriteHeader (http .StatusBadRequest )
304- _ = json .NewEncoder (w ).Encode ("ID parameter not present" )
305- return
306- }
342+ if flowId == "" {
343+ a .logger .Errorf ("ID parameter not present" )
344+ w .WriteHeader (http .StatusBadRequest )
345+ _ = json .NewEncoder (w ).Encode ("ID parameter not present" )
346+ return
347+ }
307348
308349 body , err := a .service .ParseRegistrationFlowMethodBody (r )
309350 if err != nil {
@@ -1056,25 +1097,11 @@ func NewAPI(
10561097}
10571098
10581099func setCookies (w http.ResponseWriter , cookies []* http.Cookie , exclude ... string ) {
1059- for _ , c := range filterCookies (cookies , exclude ... ) {
1100+ for _ , c := range httpHelpers . FilterCookies (cookies , exclude ... ) {
10601101 http .SetCookie (w , c )
10611102 }
10621103}
10631104
1064- func filterCookies (cookies []* http.Cookie , exclude ... string ) []* http.Cookie {
1065- ret := []* http.Cookie {}
1066- l1:
1067- for _ , c := range cookies {
1068- for _ , n := range exclude {
1069- if c .Name == n {
1070- continue l1
1071- }
1072- }
1073- ret = append (ret , c )
1074- }
1075- return ret
1076- }
1077-
10781105func hash (plain string ) string {
10791106 h := md5 .New ()
10801107 h .Write ([]byte (plain ))
0 commit comments