(c *gin.Context)
| 184 | } |
| 185 | |
| 186 | func (controller *UserController) totpHandler(c *gin.Context) { |
| 187 | var req TotpRequest |
| 188 | |
| 189 | err := c.ShouldBindJSON(&req) |
| 190 | if err != nil { |
| 191 | tlog.App.Error().Err(err).Msg("Failed to bind JSON") |
| 192 | c.JSON(400, gin.H{ |
| 193 | "status": 400, |
| 194 | "message": "Bad Request", |
| 195 | }) |
| 196 | return |
| 197 | } |
| 198 | |
| 199 | context, err := utils.GetContext(c) |
| 200 | |
| 201 | if err != nil { |
| 202 | tlog.App.Error().Err(err).Msg("Failed to get user context") |
| 203 | c.JSON(500, gin.H{ |
| 204 | "status": 500, |
| 205 | "message": "Internal Server Error", |
| 206 | }) |
| 207 | return |
| 208 | } |
| 209 | |
| 210 | if !context.TotpPending { |
| 211 | tlog.App.Warn().Msg("TOTP attempt without a pending TOTP session") |
| 212 | c.JSON(401, gin.H{ |
| 213 | "status": 401, |
| 214 | "message": "Unauthorized", |
| 215 | }) |
| 216 | return |
| 217 | } |
| 218 | |
| 219 | tlog.App.Debug().Str("username", context.Username).Msg("TOTP verification attempt") |
| 220 | |
| 221 | isLocked, remaining := controller.auth.IsAccountLocked(context.Username) |
| 222 | |
| 223 | if isLocked { |
| 224 | tlog.App.Warn().Str("username", context.Username).Msg("Account is locked due to too many failed TOTP attempts") |
| 225 | c.Writer.Header().Add("x-tinyauth-lock-locked", "true") |
| 226 | c.Writer.Header().Add("x-tinyauth-lock-reset", time.Now().Add(time.Duration(remaining)*time.Second).Format(time.RFC3339)) |
| 227 | c.JSON(429, gin.H{ |
| 228 | "status": 429, |
| 229 | "message": fmt.Sprintf("Too many failed TOTP attempts. Try again in %d seconds", remaining), |
| 230 | }) |
| 231 | return |
| 232 | } |
| 233 | |
| 234 | user := controller.auth.GetLocalUser(context.Username) |
| 235 | |
| 236 | ok := totp.Validate(req.Code, user.TotpSecret) |
| 237 | |
| 238 | if !ok { |
| 239 | tlog.App.Warn().Str("username", context.Username).Msg("Invalid TOTP code") |
| 240 | controller.auth.RecordLoginAttempt(context.Username, false) |
| 241 | tlog.AuditLoginFailure(c, context.Username, "totp", "invalid totp code") |
| 242 | c.JSON(401, gin.H{ |
| 243 | "status": 401, |
nothing calls this directly
no test coverage detected