2019-12-04 20:39:56 +01:00
// Vikunja is a todo-list application to facilitate your life.
2020-01-09 18:33:22 +01:00
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
2018-11-26 21:17:33 +01:00
//
2019-12-04 20:39:56 +01:00
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
2018-11-26 21:17:33 +01:00
//
2019-12-04 20:39:56 +01:00
// 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 General Public License for more details.
2018-11-26 21:17:33 +01:00
//
2019-12-04 20:39:56 +01:00
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
2018-11-26 21:17:33 +01:00
2018-11-12 16:46:35 +01:00
// @title Vikunja API
2019-01-03 23:22:06 +01:00
// @description This is the documentation for the [Vikunja](http://vikunja.io) API. Vikunja is a cross-plattform Todo-application with a lot of features, such as sharing lists with users or teams. <!-- ReDoc-Inject: <security-definitions> -->
2019-10-23 23:11:40 +02:00
// @description # Pagination
// @description Every endpoint capable of pagination will return two headers:
// @description * `x-pagination-total-pages`: The total number of available pages for this request
// @description * `x-pagination-result-count`: The number of items returned for this request.
2019-01-03 23:22:06 +01:00
// @description # Authorization
2019-10-16 22:52:29 +02:00
// @description **JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully.
2019-01-03 23:22:06 +01:00
// @description
// @description **BasicAuth:** Only used when requesting tasks via caldav.
// @description <!-- ReDoc-Inject: <security-definitions> -->
2018-11-12 16:46:35 +01:00
// @BasePath /api/v1
2019-01-03 23:22:06 +01:00
// @license.url http://code.vikunja.io/api/src/branch/master/LICENSE
// @license.name GPLv3
// @contact.url http://vikunja.io/en/contact/
// @contact.name General Vikunja contact
// @contact.email hello@vikunja.io
2018-11-12 16:46:35 +01:00
// @securityDefinitions.basic BasicAuth
2019-01-03 23:22:06 +01:00
// @securityDefinitions.apikey JWTKeyAuth
2018-11-12 16:46:35 +01:00
// @in header
// @name Authorization
2018-06-10 11:11:41 +02:00
2018-08-29 15:22:17 +02:00
package routes
2018-06-10 11:11:41 +02:00
import (
2019-07-06 22:12:26 +02:00
"code.vikunja.io/api/pkg/config"
2018-12-01 00:26:56 +01:00
"code.vikunja.io/api/pkg/log"
2018-11-17 00:17:37 +01:00
"code.vikunja.io/api/pkg/models"
2020-01-19 17:52:16 +01:00
"code.vikunja.io/api/pkg/modules/migration"
migrationHandler "code.vikunja.io/api/pkg/modules/migration/handler"
"code.vikunja.io/api/pkg/modules/migration/wunderlist"
2018-10-31 13:42:38 +01:00
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
2019-05-22 19:48:48 +02:00
"code.vikunja.io/api/pkg/routes/caldav"
2019-02-17 20:53:04 +01:00
_ "code.vikunja.io/api/pkg/swagger" // To generate swagger docs
2018-12-01 00:26:56 +01:00
"code.vikunja.io/web"
"code.vikunja.io/web/handler"
2018-11-17 00:17:37 +01:00
"github.com/asaskevich/govalidator"
2019-05-07 21:42:24 +02:00
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
2019-01-25 12:40:54 +01:00
elog "github.com/labstack/gommon/log"
2019-05-22 19:48:48 +02:00
"strings"
2018-06-10 11:11:41 +02:00
)
2018-11-17 00:17:37 +01:00
// CustomValidator is a dummy struct to use govalidator with echo
type CustomValidator struct { }
// Validate validates stuff
func ( cv * CustomValidator ) Validate ( i interface { } ) error {
if _ , err := govalidator . ValidateStruct ( i ) ; err != nil {
var errs [ ] string
for field , e := range govalidator . ErrorsByField ( err ) {
errs = append ( errs , field + ": " + e )
}
httperr := models . ValidationHTTPError {
2019-08-31 22:56:41 +02:00
HTTPError : web . HTTPError {
2018-11-17 00:17:37 +01:00
Code : models . ErrCodeInvalidData ,
Message : "Invalid Data" ,
} ,
2019-08-31 22:56:41 +02:00
InvalidFields : errs ,
2018-11-17 00:17:37 +01:00
}
return httperr
}
return nil
}
2018-06-10 11:11:41 +02:00
// NewEcho registers a new Echo instance
func NewEcho ( ) * echo . Echo {
e := echo . New ( )
2018-11-03 16:05:45 +01:00
e . HideBanner = true
2019-01-25 12:40:54 +01:00
if l , ok := e . Logger . ( * elog . Logger ) ; ok {
2019-07-06 22:12:26 +02:00
if config . LogEcho . GetString ( ) == "off" {
2019-01-25 12:40:54 +01:00
l . SetLevel ( elog . OFF )
}
l . EnableColor ( )
l . SetHeader ( log . ErrFmt )
l . SetOutput ( log . GetLogWriter ( "echo" ) )
}
2018-06-10 11:11:41 +02:00
// Logger
2019-07-06 22:12:26 +02:00
if config . LogHTTP . GetString ( ) != "off" {
2019-01-25 12:40:54 +01:00
e . Use ( middleware . LoggerWithConfig ( middleware . LoggerConfig {
Format : log . WebFmt + "\n" ,
Output : log . GetLogWriter ( "http" ) ,
} ) )
}
2018-06-10 11:11:41 +02:00
2018-11-17 00:17:37 +01:00
// Validation
e . Validator = & CustomValidator { }
2019-03-24 10:13:40 +01:00
// Handler config
handler . SetAuthProvider ( & web . Auths {
2019-09-08 21:11:42 +02:00
AuthObject : apiv1 . GetAuthFromClaims ,
2019-03-24 10:13:40 +01:00
} )
2019-07-20 20:12:10 +02:00
handler . SetLoggingProvider ( log . GetLogger ( ) )
2019-10-23 23:11:40 +02:00
handler . SetMaxItemsPerPage ( config . ServiceMaxItemsPerPage . GetInt ( ) )
2019-03-24 10:13:40 +01:00
2018-06-10 11:11:41 +02:00
return e
}
// RegisterRoutes registers all routes for the application
func RegisterRoutes ( e * echo . Echo ) {
2019-07-06 22:12:26 +02:00
if config . ServiceEnableCaldav . GetBool ( ) {
2019-05-22 19:48:48 +02:00
// Caldav routes
wkg := e . Group ( "/.well-known" )
wkg . Use ( middleware . BasicAuth ( caldavBasicAuth ) )
wkg . Any ( "/caldav" , caldav . PrincipalHandler )
wkg . Any ( "/caldav/" , caldav . PrincipalHandler )
c := e . Group ( "/dav" )
registerCalDavRoutes ( c )
}
2018-09-07 22:49:16 +02:00
// CORS_SHIT
e . Use ( middleware . CORSWithConfig ( middleware . CORSConfig {
AllowOrigins : [ ] string { "*" } ,
2019-05-22 19:48:48 +02:00
Skipper : func ( context echo . Context ) bool {
// Since it is not possible to register this middleware just for the api group,
// we just disable it when for caldav requests.
// Caldav requires OPTIONS requests to be answered in a specific manner,
// not doing this would break the caldav implementation
return strings . HasPrefix ( context . Path ( ) , "/dav" )
} ,
2018-09-07 22:49:16 +02:00
} ) )
2018-06-10 11:11:41 +02:00
// API Routes
a := e . Group ( "/api/v1" )
2019-05-22 19:48:48 +02:00
registerAPIRoutes ( a )
}
func registerAPIRoutes ( a * echo . Group ) {
2018-06-10 11:11:41 +02:00
2019-01-03 23:22:06 +01:00
// Docs
a . GET ( "/docs.json" , apiv1 . DocsJSON )
a . GET ( "/docs" , apiv1 . RedocUI )
2018-09-17 18:34:46 +02:00
2018-12-12 23:50:35 +01:00
// Prometheus endpoint
2019-07-21 23:44:12 +02:00
setupMetrics ( a )
2018-12-12 23:50:35 +01:00
// User stuff
2018-06-10 11:34:59 +02:00
a . POST ( "/login" , apiv1 . Login )
2018-06-13 13:45:22 +02:00
a . POST ( "/register" , apiv1 . RegisterUser )
2018-10-27 11:33:28 +02:00
a . POST ( "/user/password/token" , apiv1 . UserRequestResetPasswordToken )
a . POST ( "/user/password/reset" , apiv1 . UserResetPassword )
2018-10-27 15:14:55 +02:00
a . POST ( "/user/confirm" , apiv1 . UserConfirmEmail )
2018-06-10 11:11:41 +02:00
2019-07-16 00:54:38 +02:00
// Info endpoint
a . GET ( "/info" , apiv1 . Info )
2019-08-31 22:56:41 +02:00
// Link share auth
if config . ServiceEnableLinkSharing . GetBool ( ) {
a . POST ( "/shares/:share/auth" , apiv1 . AuthenticateLinkShare )
}
2019-07-21 23:27:30 +02:00
// ===== Routes with Authetication =====
2018-06-10 11:11:41 +02:00
// Authetification
2019-07-06 22:12:26 +02:00
a . Use ( middleware . JWT ( [ ] byte ( config . ServiceJWTSecret . GetString ( ) ) ) )
2018-12-01 00:26:56 +01:00
2019-07-21 23:27:30 +02:00
// Rate limit
setupRateLimit ( a )
2018-12-12 23:50:35 +01:00
// Middleware to collect metrics
2019-07-21 23:44:12 +02:00
setupMetricsMiddleware ( a )
2018-12-12 23:50:35 +01:00
2018-06-10 11:11:41 +02:00
a . POST ( "/tokenTest" , apiv1 . CheckToken )
2018-06-10 14:14:10 +02:00
2018-09-20 19:42:01 +02:00
// User stuff
a . GET ( "/user" , apiv1 . UserShow )
2018-10-03 19:28:17 +02:00
a . POST ( "/user/password" , apiv1 . UserChangePassword )
2018-09-20 19:42:01 +02:00
a . GET ( "/users" , apiv1 . UserList )
2019-12-07 20:52:04 +01:00
a . POST ( "/user/token" , apiv1 . RenewToken )
2018-09-20 19:42:01 +02:00
2018-12-01 00:26:56 +01:00
listHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . List { }
} ,
2018-07-07 14:19:34 +02:00
}
2018-07-08 22:50:01 +02:00
a . GET ( "/lists" , listHandler . ReadAllWeb )
2018-07-21 15:08:46 +02:00
a . GET ( "/lists/:list" , listHandler . ReadOneWeb )
a . POST ( "/lists/:list" , listHandler . UpdateWeb )
a . DELETE ( "/lists/:list" , listHandler . DeleteWeb )
a . PUT ( "/namespaces/:namespace/lists" , listHandler . CreateWeb )
2019-07-18 18:38:21 +02:00
a . GET ( "/lists/:list/listusers" , apiv1 . ListUsersForList )
2018-06-12 18:07:47 +02:00
2019-08-31 22:56:41 +02:00
if config . ServiceEnableLinkSharing . GetBool ( ) {
listSharingHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . LinkSharing { }
} ,
}
a . PUT ( "/lists/:list/shares" , listSharingHandler . CreateWeb )
a . GET ( "/lists/:list/shares" , listSharingHandler . ReadAllWeb )
a . GET ( "/lists/:list/shares/:share" , listSharingHandler . ReadOneWeb )
a . DELETE ( "/lists/:list/shares/:share" , listSharingHandler . DeleteWeb )
}
2019-12-01 14:38:11 +01:00
taskCollectionHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . TaskCollection { }
} ,
}
a . GET ( "/lists/:list/tasks" , taskCollectionHandler . ReadAllWeb )
2018-12-01 00:26:56 +01:00
taskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2019-08-14 22:19:04 +02:00
return & models . Task { }
2018-10-11 17:53:59 +02:00
} ,
2018-07-11 02:13:53 +02:00
}
2018-08-30 08:09:17 +02:00
a . PUT ( "/lists/:list" , taskHandler . CreateWeb )
2019-11-02 21:33:18 +01:00
a . GET ( "/tasks/:listtask" , taskHandler . ReadOneWeb )
2019-12-01 14:38:11 +01:00
a . GET ( "/tasks/all" , taskCollectionHandler . ReadAllWeb )
2018-08-30 08:09:17 +02:00
a . DELETE ( "/tasks/:listtask" , taskHandler . DeleteWeb )
a . POST ( "/tasks/:listtask" , taskHandler . UpdateWeb )
2018-07-02 08:40:24 +02:00
2018-12-28 22:49:46 +01:00
bulkTaskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . BulkTask { }
} ,
}
a . POST ( "/tasks/bulk" , bulkTaskHandler . UpdateWeb )
2019-01-08 20:13:07 +01:00
assigneeTaskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2019-08-14 22:19:04 +02:00
return & models . TaskAssginee { }
2019-01-08 20:13:07 +01:00
} ,
}
a . PUT ( "/tasks/:listtask/assignees" , assigneeTaskHandler . CreateWeb )
a . DELETE ( "/tasks/:listtask/assignees/:user" , assigneeTaskHandler . DeleteWeb )
a . GET ( "/tasks/:listtask/assignees" , assigneeTaskHandler . ReadAllWeb )
bulkAssigneeHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . BulkAssignees { }
} ,
}
2019-01-10 00:08:12 +01:00
a . POST ( "/tasks/:listtask/assignees/bulk" , bulkAssigneeHandler . CreateWeb )
2019-01-08 20:13:07 +01:00
2018-12-31 02:18:41 +01:00
labelTaskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . LabelTask { }
} ,
}
a . PUT ( "/tasks/:listtask/labels" , labelTaskHandler . CreateWeb )
a . DELETE ( "/tasks/:listtask/labels/:label" , labelTaskHandler . DeleteWeb )
a . GET ( "/tasks/:listtask/labels" , labelTaskHandler . ReadAllWeb )
2019-01-10 00:08:12 +01:00
bulkLabelTaskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . LabelTaskBulk { }
} ,
}
a . POST ( "/tasks/:listtask/labels/bulk" , bulkLabelTaskHandler . CreateWeb )
2019-09-25 20:44:41 +02:00
taskRelationHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . TaskRelation { }
} ,
}
a . PUT ( "/tasks/:task/relations" , taskRelationHandler . CreateWeb )
a . DELETE ( "/tasks/:task/relations" , taskRelationHandler . DeleteWeb )
2019-10-16 22:52:29 +02:00
taskAttachmentHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . TaskAttachment { }
} ,
}
a . GET ( "/tasks/:task/attachments" , taskAttachmentHandler . ReadAllWeb )
a . DELETE ( "/tasks/:task/attachments/:attachment" , taskAttachmentHandler . DeleteWeb )
a . PUT ( "/tasks/:task/attachments" , apiv1 . UploadTaskAttachment )
a . GET ( "/tasks/:task/attachments/:attachment" , apiv1 . GetTaskAttachment )
2018-12-31 02:18:41 +01:00
labelHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . Label { }
} ,
}
a . GET ( "/labels" , labelHandler . ReadAllWeb )
a . GET ( "/labels/:label" , labelHandler . ReadOneWeb )
a . PUT ( "/labels" , labelHandler . CreateWeb )
a . DELETE ( "/labels/:label" , labelHandler . DeleteWeb )
a . POST ( "/labels/:label" , labelHandler . UpdateWeb )
2018-12-01 00:26:56 +01:00
listTeamHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . TeamList { }
} ,
2018-07-24 17:29:13 +02:00
}
a . GET ( "/lists/:list/teams" , listTeamHandler . ReadAllWeb )
a . PUT ( "/lists/:list/teams" , listTeamHandler . CreateWeb )
a . DELETE ( "/lists/:list/teams/:team" , listTeamHandler . DeleteWeb )
2018-09-19 08:08:41 +02:00
a . POST ( "/lists/:list/teams/:team" , listTeamHandler . UpdateWeb )
2018-07-24 17:29:13 +02:00
2018-12-01 00:26:56 +01:00
listUserHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . ListUser { }
} ,
2018-08-30 08:58:09 +02:00
}
a . GET ( "/lists/:list/users" , listUserHandler . ReadAllWeb )
a . PUT ( "/lists/:list/users" , listUserHandler . CreateWeb )
a . DELETE ( "/lists/:list/users/:user" , listUserHandler . DeleteWeb )
2018-09-19 07:54:47 +02:00
a . POST ( "/lists/:list/users/:user" , listUserHandler . UpdateWeb )
2018-08-30 08:58:09 +02:00
2018-12-01 00:26:56 +01:00
namespaceHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . Namespace { }
} ,
2018-07-11 13:00:00 +02:00
}
a . GET ( "/namespaces" , namespaceHandler . ReadAllWeb )
2018-07-12 00:09:16 +02:00
a . PUT ( "/namespaces" , namespaceHandler . CreateWeb )
2018-07-21 15:08:46 +02:00
a . GET ( "/namespaces/:namespace" , namespaceHandler . ReadOneWeb )
a . POST ( "/namespaces/:namespace" , namespaceHandler . UpdateWeb )
a . DELETE ( "/namespaces/:namespace" , namespaceHandler . DeleteWeb )
a . GET ( "/namespaces/:namespace/lists" , apiv1 . GetListsByNamespaceID )
2018-07-14 17:34:59 +02:00
2018-12-01 00:26:56 +01:00
namespaceTeamHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . TeamNamespace { }
} ,
2018-07-17 08:44:21 +02:00
}
2018-07-18 21:54:04 +02:00
a . GET ( "/namespaces/:namespace/teams" , namespaceTeamHandler . ReadAllWeb )
a . PUT ( "/namespaces/:namespace/teams" , namespaceTeamHandler . CreateWeb )
a . DELETE ( "/namespaces/:namespace/teams/:team" , namespaceTeamHandler . DeleteWeb )
2018-09-19 08:16:04 +02:00
a . POST ( "/namespaces/:namespace/teams/:team" , namespaceTeamHandler . UpdateWeb )
2018-07-17 08:44:21 +02:00
2018-12-01 00:26:56 +01:00
namespaceUserHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . NamespaceUser { }
} ,
2018-09-04 20:15:24 +02:00
}
a . GET ( "/namespaces/:namespace/users" , namespaceUserHandler . ReadAllWeb )
a . PUT ( "/namespaces/:namespace/users" , namespaceUserHandler . CreateWeb )
a . DELETE ( "/namespaces/:namespace/users/:user" , namespaceUserHandler . DeleteWeb )
2018-09-19 08:03:39 +02:00
a . POST ( "/namespaces/:namespace/users/:user" , namespaceUserHandler . UpdateWeb )
2018-09-04 20:15:24 +02:00
2018-12-01 00:26:56 +01:00
teamHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . Team { }
} ,
2018-07-14 17:34:59 +02:00
}
a . GET ( "/teams" , teamHandler . ReadAllWeb )
2018-07-21 15:08:46 +02:00
a . GET ( "/teams/:team" , teamHandler . ReadOneWeb )
2018-07-14 17:34:59 +02:00
a . PUT ( "/teams" , teamHandler . CreateWeb )
2018-07-21 15:08:46 +02:00
a . POST ( "/teams/:team" , teamHandler . UpdateWeb )
a . DELETE ( "/teams/:team" , teamHandler . DeleteWeb )
2018-07-26 09:53:32 +02:00
2018-12-01 00:26:56 +01:00
teamMemberHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . TeamMember { }
} ,
2018-07-26 09:53:32 +02:00
}
a . PUT ( "/teams/:team/members" , teamMemberHandler . CreateWeb )
a . DELETE ( "/teams/:team/members/:user" , teamMemberHandler . DeleteWeb )
2020-01-19 17:52:16 +01:00
// Migrations
m := a . Group ( "/migration" )
// Wunderlist
if config . MigrationWunderlistEnable . GetBool ( ) {
wunderlistMigrationHandler := & migrationHandler . MigrationWeb {
MigrationStruct : func ( ) migration . Migrator {
return & wunderlist . Migration { }
} ,
}
m . GET ( "/wunderlist/auth" , wunderlistMigrationHandler . AuthURL )
m . POST ( "/wunderlist/migrate" , wunderlistMigrationHandler . Migrate )
}
2018-06-10 11:11:41 +02:00
}
2019-05-22 19:48:48 +02:00
func registerCalDavRoutes ( c * echo . Group ) {
// Basic auth middleware
c . Use ( middleware . BasicAuth ( caldavBasicAuth ) )
// THIS is the entry point for caldav clients, otherwise lists will show up double
c . Any ( "" , caldav . EntryHandler )
c . Any ( "/" , caldav . EntryHandler )
c . Any ( "/principals/*/" , caldav . PrincipalHandler )
c . Any ( "/lists" , caldav . ListHandler )
c . Any ( "/lists/" , caldav . ListHandler )
c . Any ( "/lists/:list" , caldav . ListHandler )
c . Any ( "/lists/:list/" , caldav . ListHandler )
c . Any ( "/lists/:list/:task" , caldav . TaskHandler ) // Mostly used for editing
}
func caldavBasicAuth ( username , password string , c echo . Context ) ( bool , error ) {
creds := & models . UserLogin {
Username : username ,
Password : password ,
}
u , err := models . CheckUserCredentials ( creds )
if err != nil {
2019-07-20 20:12:10 +02:00
log . Errorf ( "Error during basic auth for caldav: %v" , err )
2019-05-22 19:48:48 +02:00
return false , nil
}
// Save the user in echo context for later use
c . Set ( "userBasicAuth" , u )
return true , nil
}