// Vikunja is a to-do list application to facilitate your life. // Copyright 2018-2021 Vikunja and contributors. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public Licensee as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public Licensee for more details. // // You should have received a copy of the GNU Affero General Public Licensee // along with this program. If not, see . package v1 import ( "bytes" "errors" "fmt" "image/jpeg" "net/http" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" ) // UserTOTPEnroll is the handler to enroll a user into totp // @Summary Enroll a user into totp // @Description Creates an initial setup for the user in the db. After this step, the user needs to verify they have a working totp setup with the "enable totp" endpoint. // @tags user // @Accept json // @Produce json // @Security JWTKeyAuth // @Success 200 {object} user.TOTP // @Failure 400 {object} web.HTTPError "Something's invalid." // @Failure 404 {object} web.HTTPError "User does not exist." // @Failure 500 {object} models.Message "Internal server error." // @Router /user/settings/totp/enroll [post] func UserTOTPEnroll(c echo.Context) error { u, err := user.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } s := db.NewSession() defer s.Close() t, err := user.EnrollTOTP(s, u) if err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } if err := s.Commit(); err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } return c.JSON(http.StatusOK, t) } // UserTOTPEnable is the handler to enable totp for a user // @Summary Enable a previously enrolled totp setting. // @Description Enables a previously enrolled totp setting by providing a totp passcode. // @tags user // @Accept json // @Produce json // @Param totp body user.TOTPPasscode true "The totp passcode." // @Security JWTKeyAuth // @Success 200 {object} models.Message "Successfully enabled" // @Failure 400 {object} web.HTTPError "Something's invalid." // @Failure 404 {object} web.HTTPError "User does not exist." // @Failure 412 {object} web.HTTPError "TOTP is not enrolled." // @Failure 500 {object} models.Message "Internal server error." // @Router /user/settings/totp/enable [post] func UserTOTPEnable(c echo.Context) error { u, err := user.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } passcode := &user.TOTPPasscode{ User: u, } if err := c.Bind(passcode); err != nil { log.Debugf("Invalid model error. Internal error was: %s", err.Error()) var he *echo.HTTPError if errors.As(err, &he) { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)) } return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.") } s := db.NewSession() defer s.Close() err = user.EnableTOTP(s, passcode) if err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } if err := s.Commit(); err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } return c.JSON(http.StatusOK, models.Message{Message: "TOTP was enabled successfully."}) } // UserTOTPDisable disables totp settings for the current user. // @Summary Disable totp settings // @Description Disables any totp settings for the current user. // @tags user // @Accept json // @Produce json // @Security JWTKeyAuth // @Param totp body user.Login true "The current user's password (only password is enough)." // @Success 200 {object} models.Message "Successfully disabled" // @Failure 400 {object} web.HTTPError "Something's invalid." // @Failure 404 {object} web.HTTPError "User does not exist." // @Failure 500 {object} models.Message "Internal server error." // @Router /user/settings/totp/disable [post] func UserTOTPDisable(c echo.Context) error { login := &user.Login{} if err := c.Bind(login); err != nil { log.Debugf("Invalid model error. Internal error was: %s", err.Error()) var he *echo.HTTPError if errors.As(err, &he) { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)) } return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.") } u, err := user.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } s := db.NewSession() defer s.Close() u, err = user.GetUserByID(s, u.ID) if err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } err = user.CheckUserPassword(u, login.Password) if err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } err = user.DisableTOTP(s, u) if err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } if err := s.Commit(); err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } return c.JSON(http.StatusOK, models.Message{Message: "TOTP was enabled successfully."}) } // UserTOTPQrCode is the handler to show a qr code to enroll the user into totp // @Summary Totp QR Code // @Description Returns a qr code for easier setup at end user's devices. // @tags user // @Accept json // @Produce json // @Security JWTKeyAuth // @Success 200 {} string "The qr code as jpeg image" // @Failure 500 {object} models.Message "Internal server error." // @Router /user/settings/totp/qrcode [get] func UserTOTPQrCode(c echo.Context) error { u, err := user.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } s := db.NewSession() defer s.Close() qrcode, err := user.GetTOTPQrCodeForUser(s, u) if err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } buff := &bytes.Buffer{} err = jpeg.Encode(buff, qrcode, nil) if err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } if err := s.Commit(); err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } return c.Blob(http.StatusOK, "image/jpeg", buff.Bytes()) } // UserTOTP returns the current totp implementation if any is enabled. // @Summary Totp setting for the current user // @Description Returns the current user totp setting or an error if it is not enabled. // @tags user // @Accept json // @Produce json // @Security JWTKeyAuth // @Success 200 {object} user.TOTP "The totp settings." // @Failure 500 {object} models.Message "Internal server error." // @Router /user/settings/totp [get] func UserTOTP(c echo.Context) error { u, err := user.GetCurrentUser(c) if err != nil { return handler.HandleHTTPError(err, c) } s := db.NewSession() defer s.Close() t, err := user.GetTOTPForUser(s, u) if err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } if err := s.Commit(); err != nil { _ = s.Rollback() return handler.HandleHTTPError(err, c) } return c.JSON(http.StatusOK, t) }