github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/auth/auth.go (about) 1 // Package auth provides register and login handlers 2 package auth 3 4 import ( 5 "errors" 6 "fmt" 7 "net/http" 8 "net/url" 9 "strconv" 10 "strings" 11 12 "github.com/cozy/cozy-stack/model/app" 13 "github.com/cozy/cozy-stack/model/bitwarden/settings" 14 "github.com/cozy/cozy-stack/model/instance" 15 "github.com/cozy/cozy-stack/model/instance/lifecycle" 16 "github.com/cozy/cozy-stack/model/session" 17 csettings "github.com/cozy/cozy-stack/model/settings" 18 build "github.com/cozy/cozy-stack/pkg/config" 19 "github.com/cozy/cozy-stack/pkg/config/config" 20 "github.com/cozy/cozy-stack/pkg/crypto" 21 "github.com/cozy/cozy-stack/pkg/limits" 22 "github.com/cozy/cozy-stack/web/middlewares" 23 "github.com/labstack/echo/v4" 24 ) 25 26 const ( 27 // CredentialsErrorKey is the key for translating the message showed to the 28 // user when he/she enters incorrect credentials 29 CredentialsErrorKey = "Login Credentials error" 30 // TwoFactorErrorKey is the key for translating the message showed to the 31 // user when he/she enters incorrect two factor secret 32 TwoFactorErrorKey = "Login Two factor error" 33 // TwoFactorExceededErrorKey is the key for translating the message showed to the 34 // user when there were too many attempts 35 TwoFactorExceededErrorKey = "Login Two factor attempts error" 36 ) 37 38 func wantsJSON(c echo.Context) bool { 39 return c.Request().Header.Get(echo.HeaderAccept) == echo.MIMEApplicationJSON 40 } 41 42 func renderError(c echo.Context, code int, msg string) error { 43 instance := middlewares.GetInstance(c) 44 return c.Render(code, "error.html", echo.Map{ 45 "Domain": instance.ContextualDomain(), 46 "ContextName": instance.ContextName, 47 "Locale": instance.Locale, 48 "Title": instance.TemplateTitle(), 49 "Favicon": middlewares.Favicon(instance), 50 "Illustration": "/images/generic-error.svg", 51 "Error": msg, 52 "SupportEmail": instance.SupportEmailAddress(), 53 }) 54 } 55 56 // Home is the handler for / 57 // It redirects to the login page is the user is not yet authentified 58 // Else, it redirects to its home application (or onboarding) 59 func Home(c echo.Context) error { 60 instance := middlewares.GetInstance(c) 61 62 if len(instance.RegisterToken) > 0 && !instance.OnboardingFinished { 63 if !middlewares.CheckRegisterToken(c, instance) { 64 return middlewares.RenderNeedOnboarding(c, instance) 65 } 66 return c.Redirect(http.StatusSeeOther, instance.PageURL("/auth/passphrase", c.QueryParams())) 67 } 68 69 if middlewares.IsLoggedIn(c) { 70 redirect := instance.DefaultRedirection() 71 return c.Redirect(http.StatusSeeOther, redirect.String()) 72 } 73 74 // Onboarding to a specific app when authentication via OIDC is enabled 75 redirection := c.QueryParam("redirection") 76 if redirection != "" && instance.HasForcedOIDC() { 77 splits := strings.SplitN(redirection, "#", 2) 78 parts := strings.SplitN(splits[0], "/", 2) 79 if _, err := app.GetWebappBySlug(instance, parts[0]); err == nil { 80 u := instance.SubDomain(parts[0]) 81 if len(parts) == 2 { 82 u.Path = parts[1] 83 } 84 if len(splits) == 2 { 85 u.Fragment = splits[1] 86 } 87 q := url.Values{"redirect": {u.String()}} 88 return c.Redirect(http.StatusSeeOther, instance.PageURL("/oidc/start", q)) 89 } 90 } 91 92 params := make(url.Values) 93 if jwt := c.QueryParam("jwt"); jwt != "" { 94 params.Add("jwt", jwt) 95 } 96 if code := c.QueryParam("email_verified_code"); code != "" { 97 params.Add("email_verified_code", code) 98 } 99 return c.Redirect(http.StatusSeeOther, instance.PageURL("/auth/login", params)) 100 } 101 102 // SetCookieForNewSession creates a new session and sets the cookie on echo context 103 func SetCookieForNewSession(c echo.Context, duration session.Duration) (string, error) { 104 instance := middlewares.GetInstance(c) 105 session, err := session.New(instance, duration) 106 if err != nil { 107 return "", err 108 } 109 cookie, err := session.ToCookie() 110 if err != nil { 111 return "", err 112 } 113 c.SetCookie(cookie) 114 return session.ID(), nil 115 } 116 117 // isTrustedDevice checks if a device of an instance is trusted 118 func isTrustedDevice(c echo.Context, inst *instance.Instance) bool { 119 trustedDeviceToken := []byte(c.FormValue("trusted-device-token")) 120 return inst.ValidateTwoFactorTrustedDeviceSecret(c.Request(), trustedDeviceToken) 121 } 122 123 // hasEmailVerified checks if the email has already been verified, and if it is 124 // the case, the stack can skip the 2FA by email. 125 func hasEmailVerified(c echo.Context, inst *instance.Instance) bool { 126 code := c.FormValue("email_verified_code") 127 return inst.CheckEmailVerifiedCode(code) 128 } 129 130 func getLogoutURL(context string) string { 131 auth := config.GetConfig().Authentication 132 delegated, _ := auth[context].(map[string]interface{}) 133 oidc, _ := delegated["oidc"].(map[string]interface{}) 134 u, _ := oidc["logout_url"].(string) 135 return u 136 } 137 138 func redirectOIDC(c echo.Context, inst *instance.Instance) error { 139 if u := getLogoutURL(inst.ContextName); u != "" { 140 cookie, err := c.Cookie("logout") 141 if err == nil && cookie.Value == "1" { 142 c.SetCookie(&http.Cookie{ 143 Name: "logout", 144 Value: "", 145 MaxAge: -1, 146 Domain: session.CookieDomain(inst), 147 }) 148 return c.Redirect(http.StatusSeeOther, u) 149 } 150 } 151 152 var q url.Values 153 if redirect := c.QueryParam("redirect"); redirect != "" { 154 q = url.Values{"redirect": {redirect}} 155 } 156 return c.Redirect(http.StatusSeeOther, inst.PageURL("/oidc/start", q)) 157 } 158 159 func renderLoginForm(c echo.Context, i *instance.Instance, code int, credsErrors string, redirect *url.URL) error { 160 if i.HasForcedOIDC() { 161 return redirectOIDC(c, i) 162 } 163 hasFranceConnect := i.FranceConnectID != "" 164 165 publicName, err := csettings.PublicName(i) 166 if err != nil { 167 publicName = "" 168 } 169 170 var redirectStr string 171 var hasOAuth, hasSharing bool 172 if redirect != nil { 173 redirectStr = redirect.String() 174 hasOAuth = hasRedirectToAuthorize(i, redirect) 175 hasSharing = hasRedirectToAuthorizeSharing(i, redirect) 176 } 177 178 var title, help string 179 if c.QueryParam("msg") == "passphrase-reset-requested" { 180 title = i.Translate("Login Connect after reset requested title") 181 help = i.Translate("Login Connect after reset requested help") 182 } else if strings.Contains(redirectStr, "reconnect") { 183 title = i.Translate("Login Reconnect title") 184 help = i.Translate("Login Reconnect help") 185 } else if hasSharing { 186 title = i.Translate("Login Connect from sharing title", publicName) 187 help = i.Translate("Login Connect from sharing help") 188 } else { 189 if publicName == "" { 190 title = i.Translate("Login Welcome") 191 } else { 192 title = i.Translate("Login Welcome name", publicName) 193 } 194 help = i.Translate("Login Password help") 195 } 196 197 iterations := 0 198 if settings, err := settings.Get(i); err == nil { 199 iterations = settings.PassphraseKdfIterations 200 } 201 202 // When we have an email_verified_code, we need to ask the user their 203 // password, not send them an email with a magic link 204 emailVerifiedCode := c.QueryParam("email_verified_code") 205 magicLink := i.MagicLink 206 if emailVerifiedCode != "" { 207 magicLink = false 208 } 209 210 return c.Render(code, "login.html", echo.Map{ 211 "TemplateTitle": i.TemplateTitle(), 212 "Domain": i.ContextualDomain(), 213 "ContextName": i.ContextName, 214 "Locale": i.Locale, 215 "Favicon": middlewares.Favicon(i), 216 "CryptoPolyfill": middlewares.CryptoPolyfill(c), 217 "BottomNavBar": middlewares.BottomNavigationBar(c), 218 "Iterations": iterations, 219 "Salt": string(i.PassphraseSalt()), 220 "Title": title, 221 "PasswordHelp": help, 222 "CredentialsError": credsErrors, 223 "Redirect": redirectStr, 224 "CSRF": c.Get("csrf"), 225 "EmailVerifiedCode": emailVerifiedCode, 226 "MagicLink": magicLink, 227 "OAuth": hasOAuth, 228 "FranceConnect": hasFranceConnect, 229 }) 230 } 231 232 func loginForm(c echo.Context) error { 233 instance := middlewares.GetInstance(c) 234 235 redirect, err := checkRedirectParam(c, nil) 236 if err != nil { 237 return err 238 } 239 240 if middlewares.IsLoggedIn(c) { 241 if redirect == nil { 242 redirect = instance.DefaultRedirection() 243 } 244 return c.Redirect(http.StatusSeeOther, redirect.String()) 245 } 246 // Delegated JWT 247 if token := c.QueryParam("jwt"); token != "" { 248 err := session.CheckDelegatedJWT(instance, token) 249 if err != nil { 250 instance.Logger().Warnf("Delegated token check failed: %s", err) 251 } else { 252 sessionID, err := SetCookieForNewSession(c, session.NormalRun) 253 if err != nil { 254 return err 255 } 256 if err = session.StoreNewLoginEntry(instance, sessionID, "", c.Request(), "JWT", true); err != nil { 257 instance.Logger().Errorf("Could not store session history %q: %s", sessionID, err) 258 } 259 if redirect == nil { 260 redirect = instance.DefaultRedirection() 261 } 262 return c.Redirect(http.StatusSeeOther, redirect.String()) 263 } 264 } 265 return renderLoginForm(c, instance, http.StatusOK, "", redirect) 266 } 267 268 // newSession generates a new session, and puts a cookie for it 269 func newSession(c echo.Context, inst *instance.Instance, redirect *url.URL, duration session.Duration, logMessage string) error { 270 var clientID string 271 if hasRedirectToAuthorize(inst, redirect) { 272 // NOTE: the login scope is used by external clients for authentication. 273 // Typically, these clients are used for internal purposes, like 274 // authenticating to an external system via the cozy. For these clients 275 // we do not push a "client" notification, we only store a new login 276 // history. 277 clientID = redirect.Query().Get("client_id") 278 duration = session.ShortRun 279 } 280 281 sessionID, err := SetCookieForNewSession(c, duration) 282 if err != nil { 283 return err 284 } 285 286 if err = session.StoreNewLoginEntry(inst, sessionID, clientID, c.Request(), logMessage, true); err != nil { 287 inst.Logger().Errorf("Could not store session history %q: %s", sessionID, err) 288 } 289 290 return nil 291 } 292 293 func migrateToHashedPassphrase(inst *instance.Instance, settings *settings.Settings, passphrase []byte, iterations int) { 294 salt := inst.PassphraseSalt() 295 pass, masterKey := crypto.HashPassWithPBKDF2(passphrase, salt, iterations) 296 hash, err := crypto.GenerateFromPassphrase(pass) 297 if err != nil { 298 inst.Logger().Errorf("Could not hash the passphrase: %s", err.Error()) 299 return 300 } 301 inst.PassphraseHash = hash 302 settings.PassphraseKdfIterations = iterations 303 settings.PassphraseKdf = instance.PBKDF2_SHA256 304 settings.SecurityStamp = lifecycle.NewSecurityStamp() 305 key, encKey, err := lifecycle.CreatePassphraseKey(masterKey) 306 if err != nil { 307 inst.Logger().Errorf("Could not create passphrase key: %s", err.Error()) 308 return 309 } 310 settings.Key = key 311 pubKey, privKey, err := lifecycle.CreateKeyPair(encKey) 312 if err != nil { 313 inst.Logger().Errorf("Could not create key pair: %s", err.Error()) 314 return 315 } 316 settings.PublicKey = pubKey 317 settings.PrivateKey = privKey 318 if err := instance.Update(inst); err != nil { 319 inst.Logger().Errorf("Could not update: %s", err.Error()) 320 } 321 if err := settings.Save(inst); err != nil { 322 inst.Logger().Errorf("Could not update: %s", err.Error()) 323 } 324 } 325 326 func login(c echo.Context) error { 327 inst := middlewares.GetInstance(c) 328 329 redirect, err := checkRedirectParam(c, inst.DefaultRedirection()) 330 if err != nil { 331 return err 332 } 333 334 passphrase := []byte(c.FormValue("passphrase")) 335 longRunSession, _ := strconv.ParseBool(c.FormValue("long-run-session")) 336 337 var sessionID string 338 sess, ok := middlewares.GetSession(c) 339 if ok { // The user was already logged-in 340 sessionID = sess.ID() 341 } else if instance.CheckPassphrase(inst, passphrase) == nil { 342 iterations := crypto.DefaultPBKDF2Iterations 343 settings, err := settings.Get(inst) 344 // If the passphrase was not yet hashed on the client side, migrate it 345 if err == nil && settings.PassphraseKdfIterations == 0 { 346 migrateToHashedPassphrase(inst, settings, passphrase, iterations) 347 } 348 349 // In case the second factor authentication mode is "mail", we also 350 // check that the mail has been confirmed. If not, 2FA is not 351 // activated. 352 // If device is trusted, skip the 2FA. 353 // If the email has already been verified, skip the 2FA too. 354 if inst.HasAuthMode(instance.TwoFactorMail) && !isTrustedDevice(c, inst) && !hasEmailVerified(c, inst) { 355 twoFactorToken, err := lifecycle.SendTwoFactorPasscode(inst) 356 if err != nil { 357 return err 358 } 359 v := url.Values{} 360 v.Add("two_factor_token", string(twoFactorToken)) 361 v.Add("long_run_session", strconv.FormatBool(longRunSession)) 362 if loc := c.FormValue("redirect"); loc != "" { 363 v.Add("redirect", loc) 364 } 365 366 if wantsJSON(c) { 367 return c.JSON(http.StatusOK, echo.Map{ 368 "redirect": inst.PageURL("/auth/twofactor", v), 369 }) 370 } 371 return c.Redirect(http.StatusSeeOther, inst.PageURL("/auth/twofactor", v)) 372 } 373 } else { // Bad login passphrase 374 errorMessage := inst.Translate(CredentialsErrorKey) 375 err := config.GetRateLimiter().CheckRateLimit(inst, limits.AuthType) 376 if limits.IsLimitReachedOrExceeded(err) { 377 if err = LoginRateExceeded(inst); err != nil { 378 inst.Logger().WithNamespace("auth").Warn(err.Error()) 379 } 380 } 381 if wantsJSON(c) { 382 return c.JSON(http.StatusUnauthorized, echo.Map{ 383 "error": errorMessage, 384 }) 385 } 386 return renderLoginForm(c, inst, http.StatusUnauthorized, errorMessage, redirect) 387 } 388 389 // Successful authentication 390 // User is now logged-in, generate a new session 391 if sessionID == "" { 392 duration := session.NormalRun 393 if longRunSession { 394 duration = session.LongRun 395 } 396 err := newSession(c, inst, redirect, duration, "password") 397 if err != nil { 398 return err 399 } 400 } 401 if wantsJSON(c) { 402 return c.JSON(http.StatusOK, echo.Map{ 403 "redirect": redirect.String(), 404 }) 405 } 406 407 return c.Redirect(http.StatusSeeOther, redirect.String()) 408 } 409 410 // addLogoutCookie adds a cookie for logged-out users on instances in a context 411 // where OIDC is configured. It allows to redirects the user on the next request 412 // to a special page instead of sending them to the OIDC page (which can logs 413 // in the user again automatically). 414 func addLogoutCookie(c echo.Context, inst *instance.Instance) { 415 if u := getLogoutURL(inst.ContextName); u == "" { 416 return 417 } 418 c.SetCookie(&http.Cookie{ 419 Name: "logout", 420 Value: "1", 421 MaxAge: 10, 422 Domain: session.CookieDomain(inst), 423 Secure: !build.IsDevRelease(), 424 HttpOnly: true, 425 }) 426 } 427 428 func logout(c echo.Context) error { 429 res := c.Response() 430 origin := c.Request().Header.Get(echo.HeaderOrigin) 431 res.Header().Set(echo.HeaderAccessControlAllowOrigin, origin) 432 res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true") 433 434 inst := middlewares.GetInstance(c) 435 if !middlewares.AllowLogout(c) { 436 return c.JSON(http.StatusUnauthorized, echo.Map{ 437 "error": "The user can logout only from client-side apps", 438 }) 439 } 440 441 session, ok := middlewares.GetSession(c) 442 if ok { 443 c.SetCookie(session.Delete(inst)) 444 } 445 446 addLogoutCookie(c, inst) 447 448 return c.NoContent(http.StatusNoContent) 449 } 450 451 func logoutOthers(c echo.Context) error { 452 res := c.Response() 453 origin := c.Request().Header.Get(echo.HeaderOrigin) 454 res.Header().Set(echo.HeaderAccessControlAllowOrigin, origin) 455 res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true") 456 457 instance := middlewares.GetInstance(c) 458 if !middlewares.AllowLogout(c) { 459 return c.JSON(http.StatusUnauthorized, echo.Map{ 460 "error": "The user can logout only from client-side apps", 461 }) 462 } 463 464 sess, ok := middlewares.GetSession(c) 465 if !ok { 466 return c.JSON(http.StatusUnauthorized, echo.Map{ 467 "error": "Could not retrieve session", 468 }) 469 } 470 if err := session.DeleteOthers(instance, sess.ID()); err != nil { 471 return err 472 } 473 474 return c.NoContent(http.StatusNoContent) 475 } 476 477 func logoutPreflight(c echo.Context) error { 478 req := c.Request() 479 res := c.Response() 480 origin := req.Header.Get(echo.HeaderOrigin) 481 482 res.Header().Add(echo.HeaderVary, echo.HeaderOrigin) 483 res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod) 484 res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders) 485 res.Header().Set(echo.HeaderAccessControlAllowOrigin, origin) 486 res.Header().Set(echo.HeaderAccessControlAllowMethods, echo.DELETE) 487 res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true") 488 res.Header().Set(echo.HeaderAccessControlMaxAge, middlewares.MaxAgeCORS) 489 if h := req.Header.Get(echo.HeaderAccessControlRequestHeaders); h != "" { 490 res.Header().Set(echo.HeaderAccessControlAllowHeaders, h) 491 } 492 493 return c.NoContent(http.StatusNoContent) 494 } 495 496 // checkRedirectParam returns the optional redirect query parameter. If not 497 // empty, we check that the redirect is a subdomain of the cozy-instance. 498 func checkRedirectParam(c echo.Context, defaultRedirect *url.URL) (*url.URL, error) { 499 instance := middlewares.GetInstance(c) 500 redirect := c.FormValue("redirect") 501 if redirect == "" { 502 redirect = c.QueryParam("redirect") 503 } 504 505 // If the Cozy was moved from another address and the owner had a vault, 506 // we will show them instructions about how to import their vault. 507 settings, err := instance.SettingsDocument() 508 if err == nil && settings.M["import_vault"] == true { 509 u := url.URL{ 510 Scheme: instance.Scheme(), 511 Host: instance.ContextualDomain(), 512 Path: "/move/vault", 513 } 514 return &u, nil 515 } 516 517 if redirect == "" { 518 if defaultRedirect == nil { 519 return defaultRedirect, nil 520 } 521 redirect = defaultRedirect.String() 522 } 523 524 u, err := url.Parse(redirect) 525 if err != nil || u.Scheme == "" { 526 u, err = AppRedirection(instance, redirect) 527 } 528 if err != nil { 529 return nil, echo.NewHTTPError(http.StatusBadRequest, 530 "bad url: could not parse") 531 } 532 533 if u.Scheme != "http" && u.Scheme != "https" { 534 return nil, echo.NewHTTPError(http.StatusBadRequest, 535 "bad url: bad scheme") 536 } 537 538 if !instance.HasDomain(u.Host) { 539 instanceHost, appSlug, _ := config.SplitCozyHost(u.Host) 540 if !instance.HasDomain(instanceHost) || appSlug == "" { 541 return nil, echo.NewHTTPError(http.StatusBadRequest, 542 "bad url: should be subdomain") 543 } 544 return u, nil 545 } 546 547 // To protect against stealing authorization code with redirection, the 548 // fragment is always overridden. Most browsers keep URI fragments upon 549 // redirects, to make sure to override them, we put an empty one. 550 // 551 // see: oauthsecurity.com/#provider-in-the-middle 552 // see: 7.4.2 OAuth2 in Action 553 u.Fragment = "=" 554 return u, nil 555 } 556 557 func AppRedirection(inst *instance.Instance, redirect string) (*url.URL, error) { 558 splits := strings.SplitN(redirect, "#", 2) 559 parts := strings.SplitN(splits[0], "/", 2) 560 if _, err := app.GetWebappBySlug(inst, parts[0]); err != nil { 561 return nil, err 562 } 563 u := inst.SubDomain(parts[0]) 564 if len(parts) == 2 { 565 u.Path = parts[1] 566 } 567 if len(splits) == 2 { 568 u.Fragment = splits[1] 569 } 570 return u, nil 571 } 572 573 func resendActivationMail(c echo.Context) error { 574 inst := middlewares.GetInstance(c) 575 rate := config.GetRateLimiter() 576 if err := rate.CheckRateLimit(inst, limits.ResendOnboardingMailType); err == nil { 577 client := instance.APIManagerClient(inst) 578 if len(inst.RegisterToken) == 0 || inst.UUID == "" || client == nil { 579 return errors.New("cannot resend activation link") 580 } 581 url := fmt.Sprintf("/api/v1/instances/%s/resend", url.PathEscape(inst.UUID)) 582 if err := client.Post(url, nil); err != nil { 583 return errors.New("cannot resend activation link") 584 } 585 } 586 return c.Render(http.StatusOK, "error.html", echo.Map{ 587 "Domain": inst.ContextualDomain(), 588 "ContextName": inst.ContextName, 589 "Locale": inst.Locale, 590 "Title": inst.TemplateTitle(), 591 "Favicon": middlewares.Favicon(inst), 592 "Inverted": true, 593 "Illustration": "/images/mail-sent.svg", 594 "ErrorTitle": "Onboarding Resend activation Title", 595 "Error": "Onboarding Resend activation Body", 596 "ErrorDetail": "Onboarding Resend activation Detail", 597 "SupportEmail": inst.SupportEmailAddress(), 598 }) 599 } 600 601 // Routes sets the routing for the status service 602 func Routes(router *echo.Group) { 603 noCSRF := middlewares.CSRFWithConfig(middlewares.CSRFConfig{ 604 TokenLookup: "form:csrf_token", 605 CookieMaxAge: 3600, // 1 hour 606 CookieHTTPOnly: true, 607 CookieSecure: !build.IsDevRelease(), 608 CookieSameSite: http.SameSiteStrictMode, 609 CookiePath: "/auth", 610 }) 611 612 // Login/logout 613 router.GET("/login", loginForm, noCSRF, middlewares.CheckOnboardingNotFinished) 614 router.POST("/login", login, noCSRF, middlewares.CheckOnboardingNotFinished) 615 router.POST("/login/flagship", loginFlagship, middlewares.CheckOnboardingNotFinished) 616 router.DELETE("/login/others", logoutOthers) 617 router.OPTIONS("/login/others", logoutPreflight) 618 router.DELETE("/login", logout) 619 router.OPTIONS("/login", logoutPreflight) 620 621 // Magic links 622 router.POST("/magic_link", sendMagicLink, noCSRF) 623 router.GET("/magic_link", loginWithMagicLink, noCSRF) 624 router.POST("/magic_link/twofactor", loginWithMagicLinkAndPassword, noCSRF) 625 router.POST("/magic_link/flagship", magicLinkFlagship) 626 627 // Passphrase 628 router.GET("/passphrase_reset", passphraseResetForm, noCSRF) 629 router.POST("/passphrase_reset", passphraseReset, noCSRF) 630 router.GET("/passphrase_renew", passphraseRenewForm, noCSRF) 631 router.POST("/passphrase_renew", passphraseRenew, noCSRF) 632 router.GET("/passphrase", passphraseForm, noCSRF) 633 router.POST("/hint", sendHint) 634 router.POST("/onboarding/resend", resendActivationMail) 635 636 // Confirmation by typing 637 router.GET("/confirm", confirmForm, noCSRF) 638 router.POST("/confirm", confirmAuth, noCSRF) 639 router.GET("/confirm/:code", confirmCode) 640 641 // Register OAuth clients 642 router.POST("/register", registerClient, middlewares.AcceptJSON, middlewares.ContentTypeJSON) 643 router.GET("/register/:client-id", readClient, middlewares.AcceptJSON, checkRegistrationToken) 644 router.PUT("/register/:client-id", updateClient, middlewares.AcceptJSON, middlewares.ContentTypeJSON) 645 router.DELETE("/register/:client-id", deleteClient) 646 router.POST("/clients/:client-id/challenge", postChallenge, checkRegistrationToken) 647 router.POST("/clients/:client-id/attestation", postAttestation) 648 router.POST("/clients/:client-id/flagship", confirmFlagship) 649 650 // OAuth flow 651 authHandler := NewAuthorizeHandler(config.GetConfig().DeprecatedApps) 652 authHandler.Register(router.Group("/authorize", noCSRF)) 653 654 router.POST("/access_token", accessToken) 655 656 // Flagship app 657 router.POST("/session_code", CreateSessionCode) 658 router.POST("/tokens/konnectors/:slug", buildKonnectorToken) 659 660 // 2FA 661 router.GET("/twofactor", twoFactorForm) 662 router.POST("/twofactor", twoFactor) 663 664 // Share by link protected by password 665 router.POST("/share-by-link/password", checkPasswordForShareByLink) 666 }