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
}