[MM-37984] Allow Desktop App to authenticate via external providers outside of the app on supported servers (#23795)

* WIP

* Add rate limiting for desktop token API

* Missing mocks

* Style fixes

* Update snapshots

* Maybe use an actual redirect link :P

* Refactoring for tests

* Add tests for server

* Fix lint issue

* Fix tests

* Fix lint

* Add front-end screen component

* Component logic

* Style changes

* Quick style fix

* Lint fixes

* Initial PR feedback

* Enable logging into the browser as well when completing the login process

* Refactor to push more logic to the other component

* Remove unnecessary helper code

* Fix i18n

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Devin Binnie
2023-07-12 09:25:05 -04:00
коммит произвёл GitHub
родитель 5e3c03a0a8
Коммит abdf4e58c3
41 изменённых файлов: 1847 добавлений и 24 удалений

Просмотреть файл

@@ -0,0 +1,173 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package storetest
import (
"testing"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDesktopTokensStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("GetUserId", func(t *testing.T) { testGetUserId(t, ss) })
t.Run("Insert", func(t *testing.T) { testInsert(t, ss) })
t.Run("SetUserId", func(t *testing.T) { testSetUserId(t, ss) })
t.Run("Delete", func(t *testing.T) { testDeleteToken(t, ss) })
t.Run("DeleteByUserId", func(t *testing.T) { testDeleteByUserId(t, ss) })
t.Run("DeleteOlderThan", func(t *testing.T) { testDeleteOlderThan(t, ss) })
}
func testGetUserId(t *testing.T, ss store.Store) {
err := ss.DesktopTokens().Insert("token_with_id", 1000, nil)
require.NoError(t, err)
err = ss.DesktopTokens().SetUserId("token_with_id", 1000, "user_id")
require.NoError(t, err)
err = ss.DesktopTokens().Insert("token_without_id", 2000, nil)
require.NoError(t, err)
t.Run("get user id", func(t *testing.T) {
userId, err := ss.DesktopTokens().GetUserId("token_with_id", 1000)
assert.NoError(t, err)
assert.Equal(t, "user_id", userId)
})
t.Run("get user id - expired", func(t *testing.T) {
userId, err := ss.DesktopTokens().GetUserId("token_with_id", 10000)
assert.Error(t, err)
assert.IsType(t, &store.ErrNotFound{}, err)
assert.Equal(t, "", userId)
})
t.Run("get user id - no id set", func(t *testing.T) {
userId, err := ss.DesktopTokens().GetUserId("token_without_id", 2000)
assert.NoError(t, err)
assert.Equal(t, "", userId)
})
}
func testInsert(t *testing.T, ss store.Store) {
t.Run("insert", func(t *testing.T) {
err := ss.DesktopTokens().Insert("token", 1000, nil)
assert.NoError(t, err)
})
t.Run("insert with user id", func(t *testing.T) {
err := ss.DesktopTokens().Insert("token_2", 1000, model.NewString("user_id"))
assert.NoError(t, err)
})
t.Run("insert - token too long", func(t *testing.T) {
err := ss.DesktopTokens().Insert(
"this token is way way way WAAAAAAAAAAAAAY WAAAAAAAAAAAAAY WAAAAAAAAAAAAAY TOO LONG",
1000,
nil,
)
assert.Error(t, err)
})
}
func testSetUserId(t *testing.T, ss store.Store) {
err := ss.DesktopTokens().Insert("new_token", 1000, nil)
require.NoError(t, err)
err = ss.DesktopTokens().Insert("new_token_2", 1000, nil)
require.NoError(t, err)
t.Run("set user id", func(t *testing.T) {
err := ss.DesktopTokens().SetUserId("new_token", 1000, "user_id")
assert.NoError(t, err)
userId, err := ss.DesktopTokens().GetUserId("new_token", 1000)
assert.NoError(t, err)
assert.Equal(t, "user_id", userId)
})
t.Run("set user id - doesn't exist", func(t *testing.T) {
err := ss.DesktopTokens().SetUserId("different_token", 1000, "user_id")
assert.Error(t, err)
_, err = ss.DesktopTokens().GetUserId("different_token", 1000)
assert.Error(t, err)
})
t.Run("set user id - expired", func(t *testing.T) {
err := ss.DesktopTokens().SetUserId("new_token", 2000, "user_id")
assert.Error(t, err)
_, err = ss.DesktopTokens().GetUserId("new_token", 2000)
assert.Error(t, err)
})
t.Run("set user id - user id too long", func(t *testing.T) {
err := ss.DesktopTokens().SetUserId(
"new_token_2",
1000,
"user_id that is WAAAAAAAAAAAAAY WAAAAAAAAAAAAAY WAAAAAAAAAAAAAY TOO LONG",
)
assert.Error(t, err)
userId, err := ss.DesktopTokens().GetUserId("new_token_2", 1000)
assert.NoError(t, err)
assert.Equal(t, "", userId)
})
}
func testDeleteToken(t *testing.T, ss store.Store) {
err := ss.DesktopTokens().Insert("deleteable_token", 3000, nil)
require.NoError(t, err)
err = ss.DesktopTokens().SetUserId("deleteable_token", 3000, "user_id")
require.NoError(t, err)
t.Run("delete", func(t *testing.T) {
userId, err := ss.DesktopTokens().GetUserId("deleteable_token", 3000)
assert.NoError(t, err)
assert.Equal(t, "user_id", userId)
err = ss.DesktopTokens().Delete("deleteable_token")
assert.NoError(t, err)
_, err = ss.DesktopTokens().GetUserId("deleteable_token", 3000)
assert.Error(t, err)
})
}
func testDeleteByUserId(t *testing.T, ss store.Store) {
err := ss.DesktopTokens().Insert("deleteable_token_2", 4000, nil)
require.NoError(t, err)
err = ss.DesktopTokens().SetUserId("deleteable_token_2", 4000, "deleteable_user_id")
require.NoError(t, err)
t.Run("delete by user id", func(t *testing.T) {
userId, err := ss.DesktopTokens().GetUserId("deleteable_token_2", 3000)
assert.NoError(t, err)
assert.Equal(t, "deleteable_user_id", userId)
err = ss.DesktopTokens().DeleteByUserId("deleteable_user_id")
assert.NoError(t, err)
_, err = ss.DesktopTokens().GetUserId("deleteable_token_2", 3000)
assert.Error(t, err)
})
}
func testDeleteOlderThan(t *testing.T, ss store.Store) {
err := ss.DesktopTokens().Insert("deleteable_token_old", 1000, nil)
require.NoError(t, err)
err = ss.DesktopTokens().Insert("deleteable_token_new", 5000, nil)
require.NoError(t, err)
t.Run("delete older than", func(t *testing.T) {
_, err := ss.DesktopTokens().GetUserId("deleteable_token_old", 1000)
assert.NoError(t, err)
_, err = ss.DesktopTokens().GetUserId("deleteable_token_new", 5000)
assert.NoError(t, err)
err = ss.DesktopTokens().DeleteOlderThan(2000)
assert.NoError(t, err)
_, err = ss.DesktopTokens().GetUserId("deleteable_token_old", 1000)
assert.Error(t, err)
_, err = ss.DesktopTokens().GetUserId("deleteable_token_new", 5000)
assert.NoError(t, err)
})
}

Просмотреть файл

@@ -0,0 +1,121 @@
// Code generated by mockery v2.23.2. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import mock "github.com/stretchr/testify/mock"
// DesktopTokensStore is an autogenerated mock type for the DesktopTokensStore type
type DesktopTokensStore struct {
mock.Mock
}
// Delete provides a mock function with given fields: desktopToken
func (_m *DesktopTokensStore) Delete(desktopToken string) error {
ret := _m.Called(desktopToken)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(desktopToken)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteByUserId provides a mock function with given fields: userId
func (_m *DesktopTokensStore) DeleteByUserId(userId string) error {
ret := _m.Called(userId)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(userId)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteOlderThan provides a mock function with given fields: minCreatedAt
func (_m *DesktopTokensStore) DeleteOlderThan(minCreatedAt int64) error {
ret := _m.Called(minCreatedAt)
var r0 error
if rf, ok := ret.Get(0).(func(int64) error); ok {
r0 = rf(minCreatedAt)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetUserId provides a mock function with given fields: desktopToken, minCreatedAt
func (_m *DesktopTokensStore) GetUserId(desktopToken string, minCreatedAt int64) (string, error) {
ret := _m.Called(desktopToken, minCreatedAt)
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(string, int64) (string, error)); ok {
return rf(desktopToken, minCreatedAt)
}
if rf, ok := ret.Get(0).(func(string, int64) string); ok {
r0 = rf(desktopToken, minCreatedAt)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(string, int64) error); ok {
r1 = rf(desktopToken, minCreatedAt)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Insert provides a mock function with given fields: desktopToken, createdAt, userId
func (_m *DesktopTokensStore) Insert(desktopToken string, createdAt int64, userId *string) error {
ret := _m.Called(desktopToken, createdAt, userId)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64, *string) error); ok {
r0 = rf(desktopToken, createdAt, userId)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetUserId provides a mock function with given fields: desktopToken, minCreatedAt, userId
func (_m *DesktopTokensStore) SetUserId(desktopToken string, minCreatedAt int64, userId string) error {
ret := _m.Called(desktopToken, minCreatedAt, userId)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64, string) error); ok {
r0 = rf(desktopToken, minCreatedAt, userId)
} else {
r0 = ret.Error(0)
}
return r0
}
type mockConstructorTestingTNewDesktopTokensStore interface {
mock.TestingT
Cleanup(func())
}
// NewDesktopTokensStore creates a new instance of DesktopTokensStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewDesktopTokensStore(t mockConstructorTestingTNewDesktopTokensStore) *DesktopTokensStore {
mock := &DesktopTokensStore{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

Просмотреть файл

@@ -187,6 +187,22 @@ func (_m *Store) Context() context.Context {
return r0
}
// DesktopTokens provides a mock function with given fields:
func (_m *Store) DesktopTokens() store.DesktopTokensStore {
ret := _m.Called()
var r0 store.DesktopTokensStore
if rf, ok := ret.Get(0).(func() store.DesktopTokensStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.DesktopTokensStore)
}
}
return r0
}
// Draft provides a mock function with given fields:
func (_m *Store) Draft() store.DraftStore {
ret := _m.Called()

Просмотреть файл

@@ -61,6 +61,7 @@ type Store struct {
PostAcknowledgementStore mocks.PostAcknowledgementStore
PostPersistentNotificationStore mocks.PostPersistentNotificationStore
TrueUpReviewStore mocks.TrueUpReviewStore
DesktopTokensStore mocks.DesktopTokensStore
}
func (s *Store) SetContext(context context.Context) { s.context = context }
@@ -103,6 +104,7 @@ func (s *Store) ChannelMemberHistory() store.ChannelMemberHistoryStore {
return &s.ChannelMemberHistoryStore
}
func (s *Store) TrueUpReview() store.TrueUpReviewStore { return &s.TrueUpReviewStore }
func (s *Store) DesktopTokens() store.DesktopTokensStore { return &s.DesktopTokensStore }
func (s *Store) NotifyAdmin() store.NotifyAdminStore { return &s.NotifyAdminStore }
func (s *Store) Group() store.GroupStore { return &s.GroupStore }
func (s *Store) LinkMetadata() store.LinkMetadataStore { return &s.LinkMetadataStore }
@@ -176,5 +178,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
&s.PostPriorityStore,
&s.PostAcknowledgementStore,
&s.PostPersistentNotificationStore,
&s.DesktopTokensStore,
)
}