completeLogin creates tokens, optionally creates session for remember-me, sets cookies, returns JSON.
(w http.ResponseWriter, r *http.Request, user *models.User, rememberMe bool)
| 442 | |
| 443 | // completeLogin creates tokens, optionally creates session for remember-me, sets cookies, returns JSON. |
| 444 | func (h *AuthHandler) completeLogin(w http.ResponseWriter, r *http.Request, user *models.User, rememberMe bool) { |
| 445 | expiresIn := h.getJwtExpiresInSeconds() |
| 446 | |
| 447 | refreshExpSec := int64(7 * 24 * 3600) |
| 448 | if rememberMe { |
| 449 | refreshExpSec = 30 * 24 * 3600 |
| 450 | } |
| 451 | refreshToken, _ := h.createToken(user.ID, user.Role, refreshExpSec, "") |
| 452 | |
| 453 | // Always create/reuse a session so it shows in the active sessions list. |
| 454 | // Session reuse is keyed on X-Device-ID (stable across IP/UA changes). |
| 455 | var sessionID string |
| 456 | fingerprint := util.GenerateDeviceFingerprint(r) |
| 457 | deviceID := r.Header.Get("X-Device-ID") |
| 458 | expiresAtSession := time.Now().Add(time.Duration(refreshExpSec) * time.Second) |
| 459 | // Session rows no longer drive the TFA bypass decision — pass false/nil so the |
| 460 | // legacy tfa_remember_me / tfa_bypass_until columns stay at their defaults. |
| 461 | // Device trust lives in user_trusted_devices instead. |
| 462 | sess, sessErr := h.sessions.CreateOrReuseSession(r.Context(), user.ID, refreshToken, "", h.clientIP(r), r.UserAgent(), fingerprint, deviceID, expiresAtSession, false, nil) |
| 463 | if sessErr != nil { |
| 464 | if h.log != nil { |
| 465 | h.log.Error("session creation failed", "user_id", user.ID, "error", sessErr) |
| 466 | } |
| 467 | } else if sess != nil { |
| 468 | sessionID = sess.ID |
| 469 | } |
| 470 | |
| 471 | // Mint a device-trust token when the user asked to skip MFA on this device. |
| 472 | var trustExpiresAt time.Time |
| 473 | if rememberMe && user.TfaEnabled && h.trustedDevices != nil { |
| 474 | trustDuration := parseTfaRememberDuration(h.getTfaRememberMeExpiresIn()) |
| 475 | trustExpiresAt = time.Now().Add(trustDuration) |
| 476 | rawToken, tokenHash, genErr := store.GenerateTrustToken() |
| 477 | if genErr != nil { |
| 478 | if h.log != nil { |
| 479 | h.log.Error("trust token generation failed", "user_id", user.ID, "error", genErr) |
| 480 | } |
| 481 | } else { |
| 482 | label := buildDeviceLabel(r.UserAgent()) |
| 483 | if _, err := h.trustedDevices.Create(r.Context(), store.CreateTrustedDeviceParams{ |
| 484 | UserID: user.ID, |
| 485 | TokenHash: tokenHash, |
| 486 | DeviceID: deviceID, |
| 487 | UserAgent: r.UserAgent(), |
| 488 | IPAddress: h.clientIP(r), |
| 489 | Label: label, |
| 490 | ExpiresAt: trustExpiresAt, |
| 491 | }); err != nil { |
| 492 | if h.log != nil { |
| 493 | h.log.Error("trusted device create failed", "user_id", user.ID, "error", err) |
| 494 | } |
| 495 | } else { |
| 496 | h.setDeviceTrustCookie(w, r, rawToken, trustExpiresAt) |
| 497 | } |
| 498 | } |
| 499 | } |
| 500 | |
| 501 | accessToken, err := h.createToken(user.ID, user.Role, expiresIn, sessionID) |
no test coverage detected