snapshot of https://github.com/traggo/server.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
build / build (push) Failing after 3s

This commit is contained in:
upstream-mirror
2026-09-16 05:24:45 +05:00
commit cda7c1a474
272 changed files with 31973 additions and 0 deletions

83
device/create.go Normal file
View File

@@ -0,0 +1,83 @@
package device
import (
"context"
"errors"
"time"
"github.com/jinzhu/copier"
"github.com/traggo/server/auth"
"github.com/traggo/server/auth/rand"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/user/password"
)
var (
timeNow = time.Now
randToken = rand.Token
comparePassword = password.ComparePassword
errUserPassWrong = errors.New("username/password combination does not exist")
)
// Login creates a device.
func (r *ResolverForDevice) Login(ctx context.Context, username string, pass string, deviceName string, deviceType gqlmodel.DeviceType, cookie bool) (*gqlmodel.Login, error) {
user := new(model.User)
find := r.DB.Where("name = ?", username).Find(user)
if find.RecordNotFound() {
return nil, errUserPassWrong
}
if !comparePassword(user.Pass, []byte(pass)) {
return nil, errUserPassWrong
}
return r.createDeviceInternal(ctx, user, deviceName, deviceType, cookie)
}
// CreateDevice creates a device.
func (r *ResolverForDevice) CreateDevice(ctx context.Context, deviceName string, deviceType gqlmodel.DeviceType) (*gqlmodel.Login, error) {
user := auth.GetUser(ctx)
return r.createDeviceInternal(ctx, user, deviceName, deviceType, false)
}
func (r *ResolverForDevice) createDeviceInternal(ctx context.Context, user *model.User, deviceName string, deviceType gqlmodel.DeviceType, cookie bool) (*gqlmodel.Login, error) {
token := randToken(20)
for !r.DB.Where("token = ?", token).Find(new(model.Device)).RecordNotFound() {
token = randToken(20)
}
now := timeNow()
device := &model.Device{
Token: token,
UserID: user.ID,
Name: deviceName,
Type: model.DeviceType(deviceType),
CreatedAt: now.UTC(),
ActiveAt: now.UTC(),
}
if err := device.Type.Valid(); err != nil {
return nil, err
}
if cookie {
auth.GetCreateSession(ctx)(token, device.Type.Seconds())
}
create := r.DB.Create(device)
gqlUser := &gqlmodel.User{}
copier.Copy(gqlUser, user)
gqlDevice := &gqlmodel.Device{}
copier.Copy(gqlDevice, device)
return &gqlmodel.Login{
User: gqlUser,
Device: gqlDevice,
Token: token}, create.Error
}

271
device/create_test.go Normal file
View File

@@ -0,0 +1,271 @@
package device
import (
"bytes"
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
)
var (
existingPassword = []byte{1}
)
func TestGQL_Login_fails_userDoesNotExist(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
now := test.Time("2018-06-30T18:30:00+02:00")
timeDispose := fakeTime(now)
defer timeDispose()
resolver := ResolverForDevice{DB: db.DB}
login, err := resolver.Login(
context.Background(),
"jmattheis",
"123",
"test",
gqlmodel.DeviceTypeLongExpiry,
false)
assert.Nil(t, login)
assert.Equal(t, errUserPassWrong, err)
assertDeviceCount(t, db, 0)
}
func TestGQL_Login_fails_wrongPass(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.Create(&model.User{
Name: "jmattheis",
ID: 1,
Pass: existingPassword,
})
pwDispose := fakePassword()
defer pwDispose()
now := test.Time("2018-06-30T18:30:00+02:00")
timeDispose := fakeTime(now)
defer timeDispose()
resolver := ResolverForDevice{DB: db.DB}
login, err := resolver.Login(
context.Background(),
"jmattheis",
"123",
"test",
gqlmodel.DeviceTypeLongExpiry,
false)
assert.Nil(t, login)
assert.Equal(t, errUserPassWrong, err)
assertDeviceCount(t, db, 0)
}
func TestGQL_Login_succeeds(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.Create(&model.User{
Name: "jmattheis",
ID: 1,
Pass: existingPassword,
})
pwDispose := fakePassword()
defer pwDispose()
tokenDispose := fakeToken("firstToken")
defer tokenDispose()
now := test.Time("2018-06-30T18:30:00+02:00")
timeDispose := fakeTime(now)
defer timeDispose()
resolver := ResolverForDevice{DB: db.DB}
login, err := resolver.Login(
context.Background(),
"jmattheis",
"unicorn",
"test",
gqlmodel.DeviceTypeLongExpiry,
false)
assert.Nil(t, err)
expected := &gqlmodel.Login{
Token: "firstToken",
User: &gqlmodel.User{Admin: false, ID: 1, Name: "jmattheis"},
Device: &gqlmodel.Device{
ID: 1,
Name: "test",
Type: gqlmodel.DeviceTypeLongExpiry,
CreatedAt: model.Time(now.UTC()),
ActiveAt: model.Time(now.UTC()),
},
}
assert.Equal(t, expected, login)
assertDeviceCount(t, db, 1)
}
func TestGQL_CreateDevice_succeeds(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
user := &model.User{
Name: "jmattheis",
ID: 1,
Pass: existingPassword,
}
db.Create(user)
pwDispose := fakePassword()
defer pwDispose()
tokenDispose := fakeToken("firstToken")
defer tokenDispose()
now := test.Time("2018-06-30T18:30:00+02:00")
timeDispose := fakeTime(now)
defer timeDispose()
resolver := ResolverForDevice{DB: db.DB}
login, err := resolver.CreateDevice(
auth.WithUser(context.Background(), user),
"test",
gqlmodel.DeviceTypeLongExpiry)
assert.Nil(t, err)
expected := &gqlmodel.Login{
Token: "firstToken",
User: &gqlmodel.User{Admin: false, ID: 1, Name: "jmattheis"},
Device: &gqlmodel.Device{
ID: 1,
Name: "test",
Type: gqlmodel.DeviceTypeLongExpiry,
CreatedAt: model.Time(now.UTC()),
ActiveAt: model.Time(now.UTC()),
},
}
assert.Equal(t, expected, login)
assertDeviceCount(t, db, 1)
}
func TestGQL_Login_setsCookie(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.Create(&model.User{
Name: "jmattheis",
ID: 1,
Pass: existingPassword,
})
pwDispose := fakePassword()
defer pwDispose()
tokenDispose := fakeToken("firstToken")
defer tokenDispose()
now := test.Time("2018-06-30T18:30:00+02:00")
timeDispose := fakeTime(now)
defer timeDispose()
resolver := ResolverForDevice{DB: db.DB}
createSessionToken := ""
createSessionAge := 0
_, err := resolver.Login(
auth.WithCreateSession(context.Background(), func(token string, age int) {
createSessionToken = token
createSessionAge = age
}),
"jmattheis",
"unicorn",
"test",
gqlmodel.DeviceTypeShortExpiry,
true)
assert.Nil(t, err)
assert.Equal(t, "firstToken", createSessionToken)
assert.Equal(t, 60*60, createSessionAge)
assertDeviceCount(t, db, 1)
}
func TestGQL_Login_succeeds_withExistingToken(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.Create(&model.User{
Name: "jmattheis",
ID: 1,
Pass: existingPassword,
})
db.Create(&model.Device{
Name: "test",
ID: 1,
Token: "firstToken",
UserID: 1,
})
pwDispose := fakePassword()
defer pwDispose()
tokenDispose := fakeToken("firstToken", "secondToken")
defer tokenDispose()
now := test.Time("2018-06-30T18:30:00+02:00")
timeDispose := fakeTime(now)
defer timeDispose()
resolver := ResolverForDevice{DB: db.DB}
login, err := resolver.Login(
context.Background(),
"jmattheis",
"unicorn",
"test",
gqlmodel.DeviceTypeShortExpiry,
false)
assert.Nil(t, err)
assert.Equal(t, "secondToken", login.Token)
assertDeviceCount(t, db, 2)
}
func fakePassword() func() {
old := comparePassword
comparePassword = func(hashedPassword, password []byte) bool {
return bytes.Equal(password, []byte("unicorn")) && bytes.Equal(hashedPassword, existingPassword)
}
return func() {
comparePassword = old
}
}
func fakeTime(t time.Time) func() {
old := timeNow
timeNow = func() time.Time {
return t
}
return func() {
timeNow = old
}
}
func fakeToken(token ...string) func() {
old := randToken
remaining := token
randToken = func(n int) string {
if len(remaining) > 0 {
used := remaining[0]
remaining = remaining[1:]
return used
}
panic("oops no token specified")
}
return func() {
randToken = old
}
}

20
device/current.go Normal file
View File

@@ -0,0 +1,20 @@
package device
import (
"context"
"github.com/jinzhu/copier"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
)
// CurrentDevice returns the current device.
func (r *ResolverForDevice) CurrentDevice(ctx context.Context) (*gqlmodel.Device, error) {
device := auth.GetDevice(ctx)
if device == nil {
return nil, nil
}
var result gqlmodel.Device
copier.Copy(&result, device)
return &result, nil
}

53
device/current_test.go Normal file
View File

@@ -0,0 +1,53 @@
package device
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestGQL_CurrentDevice_withDevice(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
device := &model.Device{
ID: 2,
Name: "Browser",
Token: "abcd",
UserID: 2,
CreatedAt: test.Time("2004-06-30T18:30:00Z"),
ActiveAt: test.Time("2015-06-30T18:30:00Z"),
Type: model.TypeNoExpiry,
}
resolver := ResolverForDevice{DB: db.DB}
result, err := resolver.CurrentDevice(fake.Device(device))
require.Nil(t, err)
expected := &gqlmodel.Device{
ID: 2,
Name: "Browser",
CreatedAt: test.ModelTimeUTC("2004-06-30T18:30:00Z"),
ActiveAt: test.ModelTimeUTC("2015-06-30T18:30:00Z"),
Type: gqlmodel.DeviceTypeNoExpiry,
}
require.Equal(t, expected, result)
}
func TestGQL_CurrentDevice_noDevice(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
resolver := ResolverForDevice{DB: db.DB}
result, err := resolver.CurrentDevice(context.Background())
require.Nil(t, err)
require.Nil(t, result)
}

8
device/deviceresolver.go Normal file
View File

@@ -0,0 +1,8 @@
package device
import "github.com/jinzhu/gorm"
// ResolverForDevice resolves device specific things.
type ResolverForDevice struct {
DB *gorm.DB
}

20
device/get.go Normal file
View File

@@ -0,0 +1,20 @@
package device
import (
"context"
"github.com/jinzhu/copier"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// Devices returns all devices.
func (r *ResolverForDevice) Devices(ctx context.Context) ([]*gqlmodel.Device, error) {
user := auth.GetUser(ctx)
var devices []model.Device
find := r.DB.Where(&model.Device{UserID: user.ID}).Order("active_at DESC").Find(&devices)
var result []*gqlmodel.Device
copier.Copy(&result, &devices)
return result, find.Error
}

51
device/get_test.go Normal file
View File

@@ -0,0 +1,51 @@
package device
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestGQL_Devices(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(1)
db.User(2)
db.Create(&model.Device{
ID: 1,
Name: "Android",
Token: "abc",
UserID: 1,
CreatedAt: test.Time("2009-06-30T18:30:00Z"),
ActiveAt: test.Time("2018-06-30T18:30:00Z"),
Type: model.TypeNoExpiry,
})
db.Create(&model.Device{
ID: 2,
Name: "Browser",
Token: "abcd",
UserID: 2,
CreatedAt: test.Time("2004-06-30T18:30:00Z"),
ActiveAt: test.Time("2015-06-30T18:30:00Z"),
Type: model.TypeNoExpiry,
})
resolver := ResolverForDevice{DB: db.DB}
devices, err := resolver.Devices(fake.User(1))
require.Nil(t, err)
expected := []*gqlmodel.Device{
{
ID: 1,
Name: "Android",
CreatedAt: test.ModelTimeUTC("2009-06-30T18:30:00Z"),
ActiveAt: test.ModelTimeUTC("2018-06-30T18:30:00Z"),
Type: gqlmodel.DeviceTypeNoExpiry,
},
}
require.Equal(t, expected, devices)
}

24
device/remove.go Normal file
View File

@@ -0,0 +1,24 @@
package device
import (
"context"
"fmt"
"github.com/jinzhu/copier"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// RemoveDevice removes a device
func (r *ResolverForDevice) RemoveDevice(ctx context.Context, id int) (*gqlmodel.Device, error) {
device := model.Device{ID: id}
if r.DB.Where(&model.Device{UserID: auth.GetUser(ctx).ID}).Find(&device).RecordNotFound() {
return nil, fmt.Errorf("device not found")
}
remove := r.DB.Delete(&device)
gqlDevice := &gqlmodel.Device{}
copier.Copy(gqlDevice, &device)
return gqlDevice, remove.Error
}

18
device/remove_current.go Normal file
View File

@@ -0,0 +1,18 @@
package device
import (
"context"
"github.com/jinzhu/copier"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
)
// RemoveCurrentDevice removes the current authenticated device
func (r *ResolverForDevice) RemoveCurrentDevice(ctx context.Context) (*gqlmodel.Device, error) {
device := auth.GetDevice(ctx)
remove := r.DB.Delete(device)
gqlDevice := &gqlmodel.Device{}
copier.Copy(gqlDevice, device)
return gqlDevice, remove.Error
}

View File

@@ -0,0 +1,41 @@
package device
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestGQL_RemoveCurrentDevice_succeeds_removesDevice(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(1)
device := &model.Device{
ID: 55,
Name: "Android",
Token: "abc",
UserID: 1,
CreatedAt: test.Time("2009-06-30T18:30:00Z"),
ActiveAt: test.Time("2018-06-30T18:30:00Z"),
Type: model.TypeNoExpiry,
}
db.Create(device)
resolver := ResolverForDevice{DB: db.DB}
gqlDevice, err := resolver.RemoveCurrentDevice(fake.Device(device))
require.Nil(t, err)
expected := &gqlmodel.Device{
ID: 55,
Name: "Android",
CreatedAt: test.ModelTimeUTC("2009-06-30T18:30:00Z"),
ActiveAt: test.ModelTimeUTC("2018-06-30T18:30:00Z"),
Type: gqlmodel.DeviceTypeNoExpiry,
}
require.Equal(t, expected, gqlDevice)
assertDeviceCount(t, db, 0)
}

58
device/remove_test.go Normal file
View File

@@ -0,0 +1,58 @@
package device
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestGQL_RemoveDevice_succeeds_removesDevice(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(1)
db.Create(&model.Device{
ID: 55,
Name: "Android",
Token: "abc",
UserID: 1,
CreatedAt: test.Time("2009-06-30T18:30:00+02:00"),
ActiveAt: test.Time("2018-06-30T18:30:00+02:00"),
Type: model.TypeNoExpiry,
})
resolver := ResolverForDevice{DB: db.DB}
_, err := resolver.RemoveDevice(fake.User(1), 55)
require.Nil(t, err)
assertDeviceCount(t, db, 0)
}
func TestGQL_RemoveDevice_fails_notExistingDevice(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(1)
resolver := ResolverForDevice{DB: db.DB}
_, err := resolver.RemoveDevice(fake.User(1), 55)
require.EqualError(t, err, "device not found")
}
func TestGQL_RemoveDevice_fails_notPermission(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(1)
db.User(2)
db.Create(&model.Device{
ID: 55,
Name: "Android",
Token: "abc",
UserID: 2,
CreatedAt: test.Time("2009-06-30T18:30:00+02:00"),
ActiveAt: test.Time("2018-06-30T18:30:00+02:00"),
Type: model.TypeNoExpiry,
})
resolver := ResolverForDevice{DB: db.DB}
_, err := resolver.RemoveDevice(fake.User(1), 55)
require.EqualError(t, err, "device not found")
}

29
device/update.go Normal file
View File

@@ -0,0 +1,29 @@
package device
import (
"context"
"errors"
"github.com/jinzhu/copier"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// UpdateDevice updates a device.
func (r *ResolverForDevice) UpdateDevice(ctx context.Context, id int, name string, deviceType gqlmodel.DeviceType) (*gqlmodel.Device, error) {
device := new(model.Device)
if r.DB.Where("user_id = ?", auth.GetUser(ctx).ID).Find(device, id).RecordNotFound() {
return nil, errors.New("device not found")
}
device.Name = name
device.Type = model.DeviceType(deviceType)
if err := device.Type.Valid(); err != nil {
return nil, err
}
update := r.DB.Save(device)
gqlDevice := &gqlmodel.Device{}
copier.Copy(gqlDevice, device)
return gqlDevice, update.Error
}

94
device/update_test.go Normal file
View File

@@ -0,0 +1,94 @@
package device
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestGQL_UpdateDevice_succeeds_updatesDevice(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.Create(&model.User{
Name: "jmattheis",
ID: 1,
Admin: true,
})
db.Create(&model.Device{
Name: "old name",
ID: 1,
UserID: 1,
CreatedAt: test.Time("2009-06-30T18:30:00Z"),
ActiveAt: test.Time("2018-06-30T18:30:00Z"),
Type: model.TypeNoExpiry,
})
resolver := ResolverForDevice{DB: db.DB}
device, err := resolver.UpdateDevice(fake.User(1), 1, "updated name", gqlmodel.DeviceTypeShortExpiry)
require.Nil(t, err)
expected := &gqlmodel.Device{
Name: "updated name",
ID: 1,
CreatedAt: test.ModelTimeUTC("2009-06-30T18:30:00Z"),
ActiveAt: test.ModelTimeUTC("2018-06-30T18:30:00Z"),
Type: gqlmodel.DeviceTypeShortExpiry,
}
require.Equal(t, expected, device)
assertDeviceCount(t, db, 1)
assertDeviceExist(t, db, model.Device{
Name: "updated name",
ID: 1,
UserID: 1,
CreatedAt: test.Time("2009-06-30T18:30:00Z"),
ActiveAt: test.Time("2018-06-30T18:30:00Z"),
Type: model.TypeShortExpiry,
})
}
func TestGQL_UpdateDevice_fails_notExistingDevice(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.Create(&model.User{
Name: "jmattheis",
ID: 1,
Admin: true,
})
resolver := ResolverForDevice{DB: db.DB}
_, err := resolver.UpdateDevice(fake.User(1), 3, "tst", gqlmodel.DeviceTypeShortExpiry)
require.EqualError(t, err, "device not found")
assertDeviceCount(t, db, 0)
}
func TestGQL_UpdateDevice_fails_noPermissions(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.Create(&model.User{
Name: "jmattheis",
ID: 1,
Admin: true,
})
db.Create(&model.User{
Name: "broderp",
ID: 2,
Admin: true,
})
db.Create(&model.Device{
Name: "old name",
ID: 66,
UserID: 2,
CreatedAt: test.Time("2009-06-30T18:30:00Z"),
ActiveAt: test.Time("2018-06-30T18:30:00Z"),
Type: model.TypeNoExpiry,
})
resolver := ResolverForDevice{DB: db.DB}
_, err := resolver.UpdateDevice(fake.User(1), 66, "tst", gqlmodel.DeviceTypeNoExpiry)
require.EqualError(t, err, "device not found")
assertDeviceCount(t, db, 1)
}

23
device/utils_test.go Normal file
View File

@@ -0,0 +1,23 @@
package device
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/model"
"github.com/traggo/server/test"
)
func assertDeviceExist(t *testing.T, db *test.Database, expected model.Device) {
foundDevice := new(model.Device)
find := db.Find(foundDevice, expected.ID)
require.Nil(t, find.Error)
require.NotNil(t, foundDevice)
require.Equal(t, expected, *foundDevice)
}
func assertDeviceCount(t *testing.T, db *test.Database, expected int) {
count := new(int)
db.Model(new(model.Device)).Count(count)
require.Equal(t, expected, *count)
}