2018-11-26 21:17:33 +01:00
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018 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 General Public License 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 General Public License for more details.
//
// 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-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> -->
// @description # Authorization
// @description **JWT-Auth:** Main authorization method, used for most of the requests. Needs ` + "`" + `Authorization: Bearer <jwt-token>` + "`" + `-header to authenticate successfully.
// @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-12-12 23:50:35 +01:00
"code.vikunja.io/api/pkg/metrics"
2018-11-17 00:17:37 +01:00
"code.vikunja.io/api/pkg/models"
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"
2018-12-12 23:50:35 +01:00
"github.com/prometheus/client_golang/prometheus/promhttp"
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 {
2018-12-01 00:26:56 +01:00
web . HTTPError {
2018-11-17 00:17:37 +01:00
Code : models . ErrCodeInvalidData ,
Message : "Invalid Data" ,
} ,
errs ,
}
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 {
AuthObject : func ( c echo . Context ) ( web . Auth , error ) {
return models . GetCurrentUser ( c )
} ,
} )
handler . SetLoggingProvider ( log . Log )
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-06 22:12:26 +02:00
if config . ServiceEnableMetrics . GetBool ( ) {
2018-12-12 23:50:35 +01:00
2019-07-06 22:12:26 +02:00
if ! config . RedisEnabled . GetBool ( ) {
2018-12-12 23:50:35 +01:00
log . Log . Fatal ( "You have to enable redis in order to use metrics" )
}
2019-05-12 16:49:16 +02:00
metrics . InitMetrics ( )
2018-12-12 23:50:35 +01:00
type countable struct {
Rediskey string
Type interface { }
}
for _ , c := range [ ] countable {
{
metrics . ListCountKey ,
models . List { } ,
} ,
{
metrics . UserCountKey ,
models . User { } ,
} ,
{
metrics . NamespaceCountKey ,
models . Namespace { } ,
} ,
{
metrics . TaskCountKey ,
models . ListTask { } ,
} ,
{
metrics . TeamCountKey ,
models . Team { } ,
} ,
} {
// Set initial totals
total , err := models . GetTotalCount ( c . Type )
if err != nil {
log . Log . Fatalf ( "Could not set initial count for %v, error was %s" , c . Type , err )
}
if err := metrics . SetCount ( total , c . Rediskey ) ; err != nil {
log . Log . Fatalf ( "Could not set initial count for %v, error was %s" , c . Type , err )
}
}
2018-12-17 13:22:06 +01:00
// init active users, sometimes we'll have garbage from previous runs in redis instead
if err := metrics . SetActiveUsers ( [ ] * metrics . ActiveUser { } ) ; err != nil {
log . Log . Fatalf ( "Could not set initial count for active users, error was %s" , err )
}
2018-12-12 23:50:35 +01:00
a . GET ( "/metrics" , echo . WrapHandler ( promhttp . Handler ( ) ) )
}
// 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
// ===== Routes with Authetification =====
// 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
2018-12-12 23:50:35 +01:00
// Middleware to collect metrics
2019-07-06 22:12:26 +02:00
if config . ServiceJWTSecret . GetBool ( ) {
2018-12-12 23:50:35 +01:00
a . Use ( func ( next echo . HandlerFunc ) echo . HandlerFunc {
return func ( c echo . Context ) error {
// Update currently active users
if err := models . UpdateActiveUsersFromContext ( c ) ; err != nil {
log . Log . Error ( err )
return next ( c )
}
return next ( c )
}
} )
}
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 )
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 )
2018-06-12 18:07:47 +02:00
2018-12-01 00:26:56 +01:00
taskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . ListTask { }
} ,
2018-07-11 02:13:53 +02:00
}
2018-08-30 08:09:17 +02:00
a . PUT ( "/lists/:list" , taskHandler . CreateWeb )
2018-12-02 19:31:03 +01:00
a . GET ( "/tasks/all" , taskHandler . 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 {
return & models . ListTaskAssginee { }
} ,
}
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 )
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 )
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 {
log . Log . Errorf ( "Error during basic auth for caldav: %v" , err )
return false , nil
}
// Save the user in echo context for later use
c . Set ( "userBasicAuth" , u )
return true , nil
}