Use the username instead of a user when adding a user to a team or giving it rights (#76)

This commit is contained in:
konrad 2019-05-25 09:47:16 +00:00 committed by Gitea
parent 3fffcbd986
commit 50d1f29125
19 changed files with 121 additions and 85 deletions

View file

@ -215,9 +215,8 @@ Sorry for some of them being in German, I'll tranlate them at some point.
* [x] Fix lint errors * [x] Fix lint errors
* [x] Add settings for max open/idle connections and max connection lifetime * [x] Add settings for max open/idle connections and max connection lifetime
* [x] Reminders should use an extra table so we can make reverse lookups aka "give me all tasks with reminders in this period" which we'll need for things like email reminders notifications * [x] Reminders should use an extra table so we can make reverse lookups aka "give me all tasks with reminders in this period" which we'll need for things like email reminders notifications
* [ ] Teams and users should also have uuids (for users these can be the username) * [x] When giving a user access to a list/namespace, they should be reffered to by uuid, not numeric id
* [ ] When giving a team or user access to a list/namespace, they should be reffered to by uuid, not numeric id * [x] Adding users to a team should also use uuid
* [ ] Adding users to a team should also use uuid
* [ ] Check if the team/user really exist before updating them on lists/namespaces * [ ] Check if the team/user really exist before updating them on lists/namespaces
* [ ] Check if the email is properly obfuscated everywhere -> alter GetUser() and add a new method GetUserWithEmail * [ ] Check if the email is properly obfuscated everywhere -> alter GetUser() and add a new method GetUserWithEmail

View file

@ -22,8 +22,10 @@ import "code.vikunja.io/web"
type ListUser struct { type ListUser struct {
// The unique, numeric id of this list <-> user relation. // The unique, numeric id of this list <-> user relation.
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"namespace"` ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"namespace"`
// The user id. // The username.
UserID int64 `xorm:"int(11) not null INDEX" json:"userID" param:"user"` Username string `xorm:"-" json:"username" param:"user"`
// Used internally to reference the user
UserID int64 `xorm:"int(11) not null INDEX" json:"-"`
// The list id. // The list id.
ListID int64 `xorm:"int(11) not null INDEX" json:"-" param:"list"` ListID int64 `xorm:"int(11) not null INDEX" json:"-" param:"list"`
// The right this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. // The right this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.

View file

@ -47,9 +47,11 @@ func (lu *ListUser) Create(a web.Auth) (err error) {
} }
// Check if the user exists // Check if the user exists
if _, err = GetUserByID(lu.UserID); err != nil { user, err := GetUserByUsername(lu.Username)
if err != nil {
return err return err
} }
lu.UserID = user.ID
// Check if the user already has access or is owner of that list // Check if the user already has access or is owner of that list
// We explicitly DONT check for teams here // We explicitly DONT check for teams here

View file

@ -34,10 +34,11 @@ import _ "code.vikunja.io/web" // For swaggerdocs generation
func (lu *ListUser) Delete() (err error) { func (lu *ListUser) Delete() (err error) {
// Check if the user exists // Check if the user exists
_, err = GetUserByID(lu.UserID) user, err := GetUserByUsername(lu.Username)
if err != nil { if err != nil {
return return
} }
lu.UserID = user.ID
// Check if the user has access to the list // Check if the user has access to the list
has, err := x.Where("user_id = ? AND list_id = ?", lu.UserID, lu.ListID). has, err := x.Where("user_id = ? AND list_id = ?", lu.UserID, lu.ListID).

View file

@ -29,6 +29,7 @@ func TestListUser_Create(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
UserID int64 UserID int64
Username string
ListID int64 ListID int64
Right Right Right Right
Created int64 Created int64
@ -49,14 +50,14 @@ func TestListUser_Create(t *testing.T) {
{ {
name: "ListUsers Create normally", name: "ListUsers Create normally",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
ListID: 2, ListID: 2,
}, },
}, },
{ {
name: "ListUsers Create for duplicate", name: "ListUsers Create for duplicate",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
ListID: 2, ListID: 2,
}, },
wantErr: true, wantErr: true,
@ -65,7 +66,7 @@ func TestListUser_Create(t *testing.T) {
{ {
name: "ListUsers Create with invalid right", name: "ListUsers Create with invalid right",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
ListID: 2, ListID: 2,
Right: 500, Right: 500,
}, },
@ -75,7 +76,7 @@ func TestListUser_Create(t *testing.T) {
{ {
name: "ListUsers Create with inexisting list", name: "ListUsers Create with inexisting list",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
ListID: 2000, ListID: 2000,
}, },
wantErr: true, wantErr: true,
@ -84,7 +85,7 @@ func TestListUser_Create(t *testing.T) {
{ {
name: "ListUsers Create with inexisting user", name: "ListUsers Create with inexisting user",
fields: fields{ fields: fields{
UserID: 500, Username: "user500",
ListID: 2, ListID: 2,
}, },
wantErr: true, wantErr: true,
@ -93,7 +94,7 @@ func TestListUser_Create(t *testing.T) {
{ {
name: "ListUsers Create with the owner as shared user", name: "ListUsers Create with the owner as shared user",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
ListID: 1, ListID: 1,
}, },
wantErr: true, wantErr: true,
@ -105,6 +106,7 @@ func TestListUser_Create(t *testing.T) {
ul := &ListUser{ ul := &ListUser{
ID: tt.fields.ID, ID: tt.fields.ID,
UserID: tt.fields.UserID, UserID: tt.fields.UserID,
Username: tt.fields.Username,
ListID: tt.fields.ListID, ListID: tt.fields.ListID,
Right: tt.fields.Right, Right: tt.fields.Right,
Created: tt.fields.Created, Created: tt.fields.Created,
@ -291,7 +293,7 @@ func TestListUser_Update(t *testing.T) {
func TestListUser_Delete(t *testing.T) { func TestListUser_Delete(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
UserID int64 Username string
ListID int64 ListID int64
Right Right Right Right
Created int64 Created int64
@ -308,7 +310,7 @@ func TestListUser_Delete(t *testing.T) {
{ {
name: "Try deleting some unexistant user", name: "Try deleting some unexistant user",
fields: fields{ fields: fields{
UserID: 1000, Username: "user1000",
ListID: 2, ListID: 2,
}, },
wantErr: true, wantErr: true,
@ -317,7 +319,7 @@ func TestListUser_Delete(t *testing.T) {
{ {
name: "Try deleting a user which does not has access but exists", name: "Try deleting a user which does not has access but exists",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
ListID: 4, ListID: 4,
}, },
wantErr: true, wantErr: true,
@ -326,7 +328,7 @@ func TestListUser_Delete(t *testing.T) {
{ {
name: "Try deleting normally", name: "Try deleting normally",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
ListID: 3, ListID: 3,
}, },
}, },
@ -335,7 +337,7 @@ func TestListUser_Delete(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
lu := &ListUser{ lu := &ListUser{
ID: tt.fields.ID, ID: tt.fields.ID,
UserID: tt.fields.UserID, Username: tt.fields.Username,
ListID: tt.fields.ListID, ListID: tt.fields.ListID,
Right: tt.fields.Right, Right: tt.fields.Right,
Created: tt.fields.Created, Created: tt.fields.Created,

View file

@ -22,8 +22,9 @@ import "code.vikunja.io/web"
type NamespaceUser struct { type NamespaceUser struct {
// The unique, numeric id of this namespace <-> user relation. // The unique, numeric id of this namespace <-> user relation.
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"namespace"` ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"namespace"`
// The user id. // The username.
UserID int64 `xorm:"int(11) not null INDEX" json:"userID" param:"user"` Username string `xorm:"-" json:"userID" param:"user"`
UserID int64 `xorm:"int(11) not null INDEX" json:"-"`
// The namespace id // The namespace id
NamespaceID int64 `xorm:"int(11) not null INDEX" json:"-" param:"namespace"` NamespaceID int64 `xorm:"int(11) not null INDEX" json:"-" param:"namespace"`
// The right this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. // The right this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.

View file

@ -49,9 +49,11 @@ func (nu *NamespaceUser) Create(a web.Auth) (err error) {
} }
// Check if the user exists // Check if the user exists
if _, err = GetUserByID(nu.UserID); err != nil { user, err := GetUserByUsername(nu.Username)
if err != nil {
return err return err
} }
nu.UserID = user.ID
// Check if the user already has access or is owner of that namespace // Check if the user already has access or is owner of that namespace
// We explicitly DO NOT check for teams here // We explicitly DO NOT check for teams here

View file

@ -34,10 +34,11 @@ import _ "code.vikunja.io/web" // For swaggerdocs generation
func (nu *NamespaceUser) Delete() (err error) { func (nu *NamespaceUser) Delete() (err error) {
// Check if the user exists // Check if the user exists
_, err = GetUserByID(nu.UserID) user, err := GetUserByUsername(nu.Username)
if err != nil { if err != nil {
return return
} }
nu.UserID = user.ID
// Check if the user has access to the namespace // Check if the user has access to the namespace
has, err := x.Where("user_id = ? AND namespace_id = ?", nu.UserID, nu.NamespaceID). has, err := x.Where("user_id = ? AND namespace_id = ?", nu.UserID, nu.NamespaceID).

View file

@ -29,7 +29,7 @@ import (
func TestNamespaceUser_Create(t *testing.T) { func TestNamespaceUser_Create(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
UserID int64 Username string
NamespaceID int64 NamespaceID int64
Right Right Right Right
Created int64 Created int64
@ -50,14 +50,14 @@ func TestNamespaceUser_Create(t *testing.T) {
{ {
name: "NamespaceUsers Create normally", name: "NamespaceUsers Create normally",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
NamespaceID: 2, NamespaceID: 2,
}, },
}, },
{ {
name: "NamespaceUsers Create for duplicate", name: "NamespaceUsers Create for duplicate",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
NamespaceID: 2, NamespaceID: 2,
}, },
wantErr: true, wantErr: true,
@ -66,7 +66,7 @@ func TestNamespaceUser_Create(t *testing.T) {
{ {
name: "NamespaceUsers Create with invalid right", name: "NamespaceUsers Create with invalid right",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
NamespaceID: 2, NamespaceID: 2,
Right: 500, Right: 500,
}, },
@ -76,7 +76,7 @@ func TestNamespaceUser_Create(t *testing.T) {
{ {
name: "NamespaceUsers Create with inexisting list", name: "NamespaceUsers Create with inexisting list",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
NamespaceID: 2000, NamespaceID: 2000,
}, },
wantErr: true, wantErr: true,
@ -85,7 +85,7 @@ func TestNamespaceUser_Create(t *testing.T) {
{ {
name: "NamespaceUsers Create with inexisting user", name: "NamespaceUsers Create with inexisting user",
fields: fields{ fields: fields{
UserID: 500, Username: "user500",
NamespaceID: 2, NamespaceID: 2,
}, },
wantErr: true, wantErr: true,
@ -94,7 +94,7 @@ func TestNamespaceUser_Create(t *testing.T) {
{ {
name: "NamespaceUsers Create with the owner as shared user", name: "NamespaceUsers Create with the owner as shared user",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
NamespaceID: 1, NamespaceID: 1,
}, },
wantErr: true, wantErr: true,
@ -105,7 +105,7 @@ func TestNamespaceUser_Create(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
un := &NamespaceUser{ un := &NamespaceUser{
ID: tt.fields.ID, ID: tt.fields.ID,
UserID: tt.fields.UserID, Username: tt.fields.Username,
NamespaceID: tt.fields.NamespaceID, NamespaceID: tt.fields.NamespaceID,
Right: tt.fields.Right, Right: tt.fields.Right,
Created: tt.fields.Created, Created: tt.fields.Created,
@ -293,7 +293,7 @@ func TestNamespaceUser_Update(t *testing.T) {
func TestNamespaceUser_Delete(t *testing.T) { func TestNamespaceUser_Delete(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
UserID int64 Username string
NamespaceID int64 NamespaceID int64
Right Right Right Right
Created int64 Created int64
@ -310,7 +310,7 @@ func TestNamespaceUser_Delete(t *testing.T) {
{ {
name: "Try deleting some unexistant user", name: "Try deleting some unexistant user",
fields: fields{ fields: fields{
UserID: 1000, Username: "user1000",
NamespaceID: 2, NamespaceID: 2,
}, },
wantErr: true, wantErr: true,
@ -319,7 +319,7 @@ func TestNamespaceUser_Delete(t *testing.T) {
{ {
name: "Try deleting a user which does not has access but exists", name: "Try deleting a user which does not has access but exists",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
NamespaceID: 4, NamespaceID: 4,
}, },
wantErr: true, wantErr: true,
@ -328,7 +328,7 @@ func TestNamespaceUser_Delete(t *testing.T) {
{ {
name: "Try deleting normally", name: "Try deleting normally",
fields: fields{ fields: fields{
UserID: 1, Username: "user1",
NamespaceID: 3, NamespaceID: 3,
}, },
}, },
@ -337,7 +337,7 @@ func TestNamespaceUser_Delete(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
nu := &NamespaceUser{ nu := &NamespaceUser{
ID: tt.fields.ID, ID: tt.fields.ID,
UserID: tt.fields.UserID, Username: tt.fields.Username,
NamespaceID: tt.fields.NamespaceID, NamespaceID: tt.fields.NamespaceID,
Right: tt.fields.Right, Right: tt.fields.Right,
Created: tt.fields.Created, Created: tt.fields.Created,

View file

@ -41,10 +41,11 @@ func (tm *TeamMember) Create(a web.Auth) (err error) {
} }
// Check if the user exists // Check if the user exists
_, err = GetUserByID(tm.UserID) user, err := GetUserByUsername(tm.Username)
if err != nil { if err != nil {
return return
} }
tm.UserID = user.ID
// Check if that user is already part of the team // Check if that user is already part of the team
exists, err := x.Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID). exists, err := x.Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID).

View file

@ -37,6 +37,13 @@ func (tm *TeamMember) Delete() (err error) {
return ErrCannotDeleteLastTeamMember{tm.TeamID, tm.UserID} return ErrCannotDeleteLastTeamMember{tm.TeamID, tm.UserID}
} }
// Find the numeric user id
user, err := GetUserByUsername(tm.Username)
if err != nil {
return
}
tm.UserID = user.ID
_, err = x.Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID).Delete(&TeamMember{}) _, err = x.Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID).Delete(&TeamMember{})
return return
} }

View file

@ -26,7 +26,7 @@ func TestTeamMember_Create(t *testing.T) {
// Dummy team member // Dummy team member
dummyteammember := TeamMember{ dummyteammember := TeamMember{
TeamID: 1, TeamID: 1,
UserID: 3, Username: "user3",
} }
// Doer // Doer
@ -57,24 +57,24 @@ func TestTeamMember_Create(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// Delete the other one // Delete the other one
tm := TeamMember{TeamID: 1, UserID: 2} tm := TeamMember{TeamID: 1, Username: "user2"}
err = tm.Delete() err = tm.Delete()
assert.NoError(t, err) assert.NoError(t, err)
// Try deleting the last one // Try deleting the last one
tm = TeamMember{TeamID: 1, UserID: 1} tm = TeamMember{TeamID: 1, Username: "user1"}
err = tm.Delete() err = tm.Delete()
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrCannotDeleteLastTeamMember(err)) assert.True(t, IsErrCannotDeleteLastTeamMember(err))
// Try inserting a user which does not exist // Try inserting a user which does not exist
dummyteammember.UserID = 9484 dummyteammember.Username = "user9484"
err = dummyteammember.Create(&doer) err = dummyteammember.Create(&doer)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrUserDoesNotExist(err)) assert.True(t, IsErrUserDoesNotExist(err))
// Try adding a user to a team which does not exist // Try adding a user to a team which does not exist
tm = TeamMember{TeamID: 94824, UserID: 1} tm = TeamMember{TeamID: 94824, Username: "user1"}
err = tm.Create(&doer) err = tm.Create(&doer)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err)) assert.True(t, IsErrTeamDoesNotExist(err))

View file

@ -66,8 +66,10 @@ type TeamMember struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"` ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
// The team id. // The team id.
TeamID int64 `xorm:"int(11) not null INDEX" json:"-" param:"team"` TeamID int64 `xorm:"int(11) not null INDEX" json:"-" param:"team"`
// The id of the member. // The username of the member. We use this to prevent automated user id entering.
UserID int64 `xorm:"int(11) not null INDEX" json:"userID" param:"user"` Username string `xorm:"-" json:"username" param:"user"`
// Used under the hood to manage team members
UserID int64 `xorm:"int(11) not null INDEX" json:"-"`
// Whether or not the member is an admin of the team. See the docs for more about what a team admin can do // Whether or not the member is an admin of the team. See the docs for more about what a team admin can do
Admin bool `xorm:"tinyint(1) INDEX null" json:"admin"` Admin bool `xorm:"tinyint(1) INDEX null" json:"admin"`

View file

@ -53,7 +53,7 @@ func (t *Team) Create(a web.Auth) (err error) {
} }
// Insert the current user as member and admin // Insert the current user as member and admin
tm := TeamMember{TeamID: t.ID, UserID: doer.ID, Admin: true} tm := TeamMember{TeamID: t.ID, Username: doer.Username, Admin: true}
if err = tm.Create(doer); err != nil { if err = tm.Create(doer); err != nil {
return err return err
} }

View file

@ -119,6 +119,15 @@ func GetUserByID(id int64) (user User, err error) {
return GetUser(User{ID: id}) return GetUser(User{ID: id})
} }
// GetUserByUsername gets a user from its user name. This is an extra function to be able to add an extra error check.
func GetUserByUsername(username string) (user User, err error) {
if username == "" {
return User{}, ErrUserDoesNotExist{}
}
return GetUser(User{Username: username})
}
// GetUser gets a user object // GetUser gets a user object
func GetUser(user User) (userOut User, err error) { func GetUser(user User) (userOut User, err error) {
userOut = user userOut = user
@ -139,7 +148,7 @@ func CheckUserCredentials(u *UserLogin) (User, error) {
} }
// Check if the user exists // Check if the user exists
user, err := GetUser(User{Username: u.Username}) user, err := GetUserByUsername(u.Username)
if err != nil { if err != nil {
// hashing the password takes a long time, so we hash something to not make it clear if the username was wrong // hashing the password takes a long time, so we hash something to not make it clear if the username was wrong
bcrypt.GenerateFromPassword([]byte(u.Username), 14) bcrypt.GenerateFromPassword([]byte(u.Username), 14)

View file

@ -36,7 +36,7 @@ func CreateUser(user User) (newUser User, err error) {
// Check if the user already existst with that username // Check if the user already existst with that username
exists := true exists := true
existingUser, err := GetUser(User{Username: newUser.Username}) existingUser, err := GetUserByUsername(newUser.Username)
if err != nil { if err != nil {
if IsErrUserDoesNotExist(err) { if IsErrUserDoesNotExist(err) {
exists = false exists = false

View file

@ -1,6 +1,6 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at // This file was generated by swaggo/swag at
// 2019-05-22 19:24:37.734465408 +0200 CEST m=+0.660846954 // 2019-05-25 11:35:41.214134069 +0200 CEST m=+0.124896065
package swagger package swagger
@ -14,7 +14,7 @@ import (
var doc = `{ var doc = `{
"swagger": "2.0", "swagger": "2.0",
"info": { "info": {
"description": "\u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e", "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. \u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs ` + "`" + `Authorization: Bearer \u003cjwt-token\u003e` + "`" + `-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n\u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e",
"title": "Vikunja API", "title": "Vikunja API",
"contact": { "contact": {
"name": "General Vikunja contact", "name": "General Vikunja contact",
@ -3984,9 +3984,9 @@ var doc = `{
"description": "A unix timestamp when this relation was last updated. You cannot change this value.", "description": "A unix timestamp when this relation was last updated. You cannot change this value.",
"type": "integer" "type": "integer"
}, },
"userID": { "username": {
"description": "The user id.", "description": "The username.",
"type": "integer" "type": "string"
} }
} }
}, },
@ -4054,8 +4054,8 @@ var doc = `{
"type": "integer" "type": "integer"
}, },
"userID": { "userID": {
"description": "The user id.", "description": "The username.",
"type": "integer" "type": "string"
} }
} }
}, },
@ -4203,9 +4203,9 @@ var doc = `{
"description": "The unique, numeric id of this team member relation.", "description": "The unique, numeric id of this team member relation.",
"type": "integer" "type": "integer"
}, },
"userID": { "username": {
"description": "The id of the member.", "description": "The username of the member. We use this to prevent automated user id entering.",
"type": "integer" "type": "string"
} }
} }
}, },

View file

@ -1,7 +1,7 @@
{ {
"swagger": "2.0", "swagger": "2.0",
"info": { "info": {
"description": "\u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e", "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. \u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs ` + \"`\" + `Authorization: Bearer \u003cjwt-token\u003e` + \"`\" + `-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n\u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e",
"title": "Vikunja API", "title": "Vikunja API",
"contact": { "contact": {
"name": "General Vikunja contact", "name": "General Vikunja contact",
@ -3970,9 +3970,9 @@
"description": "A unix timestamp when this relation was last updated. You cannot change this value.", "description": "A unix timestamp when this relation was last updated. You cannot change this value.",
"type": "integer" "type": "integer"
}, },
"userID": { "username": {
"description": "The user id.", "description": "The username.",
"type": "integer" "type": "string"
} }
} }
}, },
@ -4040,8 +4040,8 @@
"type": "integer" "type": "integer"
}, },
"userID": { "userID": {
"description": "The user id.", "description": "The username.",
"type": "integer" "type": "string"
} }
} }
}, },
@ -4189,9 +4189,9 @@
"description": "The unique, numeric id of this team member relation.", "description": "The unique, numeric id of this team member relation.",
"type": "integer" "type": "integer"
}, },
"userID": { "username": {
"description": "The id of the member.", "description": "The username of the member. We use this to prevent automated user id entering.",
"type": "integer" "type": "string"
} }
} }
}, },

View file

@ -312,9 +312,9 @@ definitions:
description: A unix timestamp when this relation was last updated. You cannot description: A unix timestamp when this relation was last updated. You cannot
change this value. change this value.
type: integer type: integer
userID: username:
description: The user id. description: The username.
type: integer type: string
type: object type: object
models.Message: models.Message:
properties: properties:
@ -369,8 +369,8 @@ definitions:
change this value. change this value.
type: integer type: integer
userID: userID:
description: The user id. description: The username.
type: integer type: string
type: object type: object
models.NamespaceWithLists: models.NamespaceWithLists:
properties: properties:
@ -487,9 +487,10 @@ definitions:
id: id:
description: The unique, numeric id of this team member relation. description: The unique, numeric id of this team member relation.
type: integer type: integer
userID: username:
description: The id of the member. description: The username of the member. We use this to prevent automated
type: integer user id entering.
type: string
type: object type: object
models.TeamNamespace: models.TeamNamespace:
properties: properties:
@ -651,7 +652,13 @@ info:
email: hello@vikunja.io email: hello@vikunja.io
name: General Vikunja contact name: General Vikunja contact
url: http://vikunja.io/en/contact/ url: http://vikunja.io/en/contact/
description: '<!-- ReDoc-Inject: <security-definitions> -->' 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> -->
# Authorization
**JWT-Auth:** Main authorization method, used for most of the requests. Needs ` + "`" + `Authorization: Bearer <jwt-token>` + "`" + `-header to authenticate successfully.
**BasicAuth:** Only used when requesting tasks via caldav.
<!-- ReDoc-Inject: <security-definitions> -->
license: license:
name: GPLv3 name: GPLv3
url: http://code.vikunja.io/api/src/branch/master/LICENSE url: http://code.vikunja.io/api/src/branch/master/LICENSE