diff --git a/pkg/routes/api/v1/user_caldav_token.go b/pkg/routes/api/v1/user_caldav_token.go new file mode 100644 index 000000000..85ab78df4 --- /dev/null +++ b/pkg/routes/api/v1/user_caldav_token.go @@ -0,0 +1,83 @@ +// 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 ( + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/web/handler" + "github.com/labstack/echo/v4" + "net/http" +) + +type TokenResponse struct { + Token string `json:"token"` +} + +// GenerateCaldavToken is the handler to create a caldav token +// @Summary Generate a caldav token +// @Description Generates a caldav token which can be used for the caldav api. It is not possible to see the token again after it was generated. +// @tags user +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Success 200 {object} v1.TokenResponse +// @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/token/caldav [post] +func GenerateCaldavToken(c echo.Context) (err error) { + + u, err := user.GetCurrentUser(c) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + token, err := user.GenerateNewCaldavToken(u) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + return c.JSON(http.StatusCreated, &TokenResponse{ + Token: token.ClearTextToken, + }) +} + +// GetCaldavTokens is the handler to return a list of all caldav tokens for the current user +// @Summary Returns the caldav tokens for the current user +// @Description Return the IDs and created dates of all caldav tokens for the current user. +// @tags user +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Success 200 {array} user.Token +// @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/token/caldav [get] +func GetCaldavTokens(c echo.Context) error { + u, err := user.GetCurrentUser(c) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + tokens, err := user.GetCaldavTokens(u) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + return c.JSON(http.StatusCreated, tokens) +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 044f3a306..3ba517970 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -321,6 +321,8 @@ func registerAPIRoutes(a *echo.Group) { u.POST("/settings/general", apiv1.UpdateGeneralUserSettings) u.POST("/export/request", apiv1.RequestUserDataExport) u.POST("/export/download", apiv1.DownloadUserDataExport) + u.POST("/settings/token/caldav", apiv1.GenerateCaldavToken) + u.GET("/settings/token/caldav", apiv1.GetCaldavTokens) if config.ServiceEnableTotp.GetBool() { u.GET("/settings/totp", apiv1.UserTOTP) diff --git a/pkg/user/caldav_token.go b/pkg/user/caldav_token.go new file mode 100644 index 000000000..1beb660a3 --- /dev/null +++ b/pkg/user/caldav_token.go @@ -0,0 +1,42 @@ +// 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 user + +import "code.vikunja.io/api/pkg/db" + +func GenerateNewCaldavToken(u *User) (token *Token, err error) { + s := db.NewSession() + + t, err := generateHashedToken(s, u, TokenCaldavAuth) + if err != nil { + return nil, err + } + + return t, err +} + +func GetCaldavTokens(u *User) (tokens []*Token, err error) { + + s := db.NewSession() + + t, err := getTokensForKind(s, u, TokenCaldavAuth) + if err != nil { + return nil, err + } + + return t, err +} diff --git a/pkg/user/delete.go b/pkg/user/delete.go index c54160ded..3e1727208 100644 --- a/pkg/user/delete.go +++ b/pkg/user/delete.go @@ -87,7 +87,7 @@ func notifyUsersScheduledForDeletion() { // RequestDeletion creates a user deletion confirm token and sends a notification to the user func RequestDeletion(s *xorm.Session, user *User) (err error) { - token, err := generateNewToken(s, user, TokenAccountDeletion) + token, err := generateToken(s, user, TokenAccountDeletion) if err != nil { return err } diff --git a/pkg/user/token.go b/pkg/user/token.go index 076df9532..c7d9a277c 100644 --- a/pkg/user/token.go +++ b/pkg/user/token.go @@ -34,17 +34,19 @@ const ( TokenPasswordReset TokenEmailConfirm TokenAccountDeletion + TokenCaldavAuth tokenSize = 64 ) // Token is a token a user can use to do things like verify their email or resetting their password type Token struct { - ID int64 `xorm:"bigint autoincr not null unique pk"` - UserID int64 `xorm:"not null"` - Token string `xorm:"varchar(450) not null index"` - Kind TokenKind `xorm:"not null"` - Created time.Time `xorm:"created not null"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"` + UserID int64 `xorm:"not null" json:"-"` + Token string `xorm:"varchar(450) not null index" json:"-"` + ClearTextToken string `xorm:"-" json:"-"` + Kind TokenKind `xorm:"not null" json:"-"` + Created time.Time `xorm:"created not null" json:"created"` } // TableName returns the real table name for user tokens @@ -52,12 +54,25 @@ func (t *Token) TableName() string { return "user_tokens" } -func generateNewToken(s *xorm.Session, u *User, kind TokenKind) (token *Token, err error) { - token = &Token{ +func genToken(u *User, kind TokenKind) *Token { + return &Token{ UserID: u.ID, Kind: kind, Token: utils.MakeRandomString(tokenSize), } +} + +func generateToken(s *xorm.Session, u *User, kind TokenKind) (token *Token, err error) { + token = genToken(u, kind) + + _, err = s.Insert(token) + return +} + +func generateHashedToken(s *xorm.Session, u *User, kind TokenKind) (token *Token, err error) { + token = genToken(u, kind) + token.ClearTextToken = token.Token + token.Token, err = HashPassword(token.ClearTextToken) _, err = s.Insert(token) return @@ -74,6 +89,14 @@ func getToken(s *xorm.Session, token string, kind TokenKind) (t *Token, err erro return } +func getTokensForKind(s *xorm.Session, u *User, kind TokenKind) (tokens []*Token, err error) { + tokens = []*Token{} + + err = s.Where("kind = ? AND user_id = ?", kind, u.ID). + Find(&tokens) + return +} + func removeTokens(s *xorm.Session, u *User, kind TokenKind) (err error) { _, err = s.Where("user_id = ? AND kind = ?", u.ID, kind). Delete(&Token{}) diff --git a/pkg/user/update_email.go b/pkg/user/update_email.go index 6b4b35d9b..f1572ec1b 100644 --- a/pkg/user/update_email.go +++ b/pkg/user/update_email.go @@ -63,7 +63,7 @@ func UpdateEmail(s *xorm.Session, update *EmailUpdate) (err error) { } update.User.Status = StatusEmailConfirmationRequired - token, err := generateNewToken(s, update.User, TokenEmailConfirm) + token, err := generateToken(s, update.User, TokenEmailConfirm) if err != nil { return } diff --git a/pkg/user/user_create.go b/pkg/user/user_create.go index 1ccecdafd..213e9cee1 100644 --- a/pkg/user/user_create.go +++ b/pkg/user/user_create.go @@ -81,7 +81,7 @@ func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) { } user.Status = StatusEmailConfirmationRequired - token, err := generateNewToken(s, user, TokenEmailConfirm) + token, err := generateToken(s, user, TokenEmailConfirm) if err != nil { return nil, err } diff --git a/pkg/user/user_password_reset.go b/pkg/user/user_password_reset.go index 277d0bb78..0fc3290b6 100644 --- a/pkg/user/user_password_reset.go +++ b/pkg/user/user_password_reset.go @@ -112,7 +112,7 @@ func RequestUserPasswordResetTokenByEmail(s *xorm.Session, tr *PasswordTokenRequ // RequestUserPasswordResetToken sends a user a password reset email. func RequestUserPasswordResetToken(s *xorm.Session, user *User) (err error) { - token, err := generateNewToken(s, user, TokenPasswordReset) + token, err := generateToken(s, user, TokenPasswordReset) if err != nil { return }