Skip to content
This repository was archived by the owner on Aug 3, 2024. It is now read-only.

Commit b75d1db

Browse files
authored
feat: Add tunnel users from admin dashboard (#15)
* feat: Add tunnel users from admin dashboard * chore: Separate tests into unit and tunnel tests * Create test database using ubuntu image * Rotate tunnel secret key
1 parent 798b217 commit b75d1db

20 files changed

+515
-33
lines changed

.github/workflows/tests.yml .github/workflows/tunnel-tests.yml

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# This workflow will build a golang project
22
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
33

4-
name: Go Test
4+
name: Tunnel Tests
55

66
on:
77
push:
@@ -41,6 +41,10 @@ jobs:
4141
go build -ldflags="-s -w" -o beaver_server ./cmd/beaver_server
4242
go build -ldflags="-s -w" -o test_server ./tests/server.go
4343
44+
- name: Prepare test database
45+
run: |
46+
mv tests/data .
47+
4448
- name: Start test servers
4549
run: |
4650
./test_server &
@@ -50,11 +54,11 @@ jobs:
5054
5155
- name: Start test client
5256
run: |
53-
./beaver --config docs/beaver_client.yaml http 9999 --subdomain test &
57+
./beaver --config tests/beaver_client.yaml http 9999 --subdomain test &
5458
sleep 3
5559
5660
- name: Run tests
57-
run: go test -v ./...
61+
run: go test -v ./tests/...
5862

5963
- name: Kill test servers
6064
run: |

.github/workflows/unit-tests.yml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# This workflow will build a golang project
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
3+
4+
name: Unit Tests
5+
6+
on:
7+
push:
8+
branches: ["main"]
9+
pull_request:
10+
branches: ["main"]
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v3
17+
18+
- name: Set up Go
19+
uses: actions/setup-go@v3
20+
with:
21+
go-version: 1.19
22+
23+
- name: Setup node
24+
uses: actions/setup-node@v3
25+
with:
26+
node-version: 18.12.1
27+
28+
- name: Build frontend
29+
run: |
30+
npm i -g pnpm
31+
pnpm install --dir internal/server/web build && pnpm run --dir internal/server/web build
32+
33+
- name: Download packages
34+
run: go mod download
35+
36+
- name: Run tests
37+
run: go test $(go list ./... | grep -v /tests) # Exclude the tests directory

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ node_modules
2222
dist/
2323
!beaver_server/
2424
data/
25-
testdata/
25+
testdata/
26+
!tests/data/

internal/server/admin/users.go

+107-3
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import (
55
"errors"
66
"fmt"
77

8+
"github.com/amalshaji/beaver/internal/utils"
89
"github.com/timshannon/badgerhold/v4"
910
)
1011

11-
var ErrAdminUserNotFound = errors.New("user does not exist")
12+
var ErrAdminUserNotFound = errors.New("admin user does not exist")
13+
var ErrTunnelUserNotFound = errors.New("tunnel user does not exist")
1214
var ErrInvalidUserSession = errors.New("invalid user session")
1315
var ErrWrongEmailOrPassword = errors.New("wrong email or password")
14-
var ErrDuplicateAdminUser = errors.New("user with the same email exists")
16+
var ErrDuplicateAdminUser = errors.New("admin user with the same email exists")
17+
var ErrDuplicateTunnelUser = errors.New("tunnel user with the same email exists")
1518

1619
type User struct {
1720
Store *badgerhold.Store
@@ -22,6 +25,8 @@ func NewUserService(store *badgerhold.Store) *User {
2225
}
2326

2427
func (u *User) findUserByEmail(ctx context.Context, email string) (*AdminUser, error) {
28+
email = utils.SanitizeString(email)
29+
2530
var superUser AdminUser
2631
if err := u.Store.FindOne(&superUser, badgerhold.Where("Email").Eq(email)); err != nil {
2732
if errors.Is(err, badgerhold.ErrNotFound) {
@@ -33,6 +38,9 @@ func (u *User) findUserByEmail(ctx context.Context, email string) (*AdminUser, e
3338
}
3439

3540
func (u *User) CreateUser(ctx context.Context, email, password string, isSuperUser bool) (*AdminUser, error) {
41+
email = utils.SanitizeString(email)
42+
password = utils.SanitizeString(password)
43+
3644
existingAdminUser, err := u.findUserByEmail(ctx, email)
3745
if err != nil && !errors.Is(err, ErrAdminUserNotFound) {
3846
return nil, err
@@ -49,7 +57,7 @@ func (u *User) CreateUser(ctx context.Context, email, password string, isSuperUs
4957
adminUser.IsSuperUser = isSuperUser
5058
adminUser.MarkAsNew()
5159

52-
if err := u.Store.Insert(badgerhold.NextSequence(), adminUser); err != nil {
60+
if err := u.Store.Insert(badgerhold.NextSequence(), &adminUser); err != nil {
5361
if errors.Is(err, badgerhold.ErrUniqueExists) {
5462
return nil, ErrDuplicateAdminUser
5563
}
@@ -68,6 +76,9 @@ func (u *User) CreateSuperUser(ctx context.Context, email, password string) (*Ad
6876
}
6977

7078
func (u *User) Login(ctx context.Context, email, password string) (string, error) {
79+
email = utils.SanitizeString(email)
80+
password = utils.SanitizeString(password)
81+
7182
var adminUser *AdminUser
7283

7384
adminUser, err := u.findUserByEmail(ctx, email)
@@ -124,3 +135,96 @@ func (u *User) ValidateSession(ctx context.Context, sessionToken string) (*Admin
124135
}
125136
return &adminUser, nil
126137
}
138+
139+
func (u *User) findTunnelUserByEmail(ctx context.Context, email string) (*TunnelUser, error) {
140+
email = utils.SanitizeString(email)
141+
142+
var tunnelUser TunnelUser
143+
144+
if err := u.Store.FindOne(&tunnelUser, badgerhold.Where("Email").Eq(email)); err != nil {
145+
if errors.Is(err, badgerhold.ErrNotFound) {
146+
return nil, ErrTunnelUserNotFound
147+
}
148+
return nil, err
149+
}
150+
return &tunnelUser, nil
151+
}
152+
153+
func (u *User) CreateTunnelUser(ctx context.Context, email string) (*TunnelUser, error) {
154+
email = utils.SanitizeString(email)
155+
156+
existingTunnelUser, err := u.findTunnelUserByEmail(ctx, email)
157+
if err != nil && !errors.Is(err, ErrTunnelUserNotFound) {
158+
return nil, err
159+
}
160+
161+
if existingTunnelUser != nil {
162+
return nil, ErrDuplicateTunnelUser
163+
}
164+
165+
if err := utils.ValidateEmail(email); err != nil {
166+
return nil, fmt.Errorf("enter a valid email address")
167+
}
168+
169+
var tunnelUser TunnelUser
170+
171+
tunnelUser.Email = email
172+
tunnelUser.RotateSecretKey()
173+
tunnelUser.MarkAsNew()
174+
175+
if err := u.Store.Insert(badgerhold.NextSequence(), &tunnelUser); err != nil {
176+
if errors.Is(err, badgerhold.ErrUniqueExists) {
177+
return nil, ErrDuplicateTunnelUser
178+
}
179+
return nil, err
180+
}
181+
182+
return &tunnelUser, nil
183+
}
184+
185+
func (u *User) GetTunnelUserBySecret(ctx context.Context, secretKey string) (*TunnelUser, error) {
186+
secretKey = utils.SanitizeString(secretKey)
187+
188+
var tunnelUser TunnelUser
189+
190+
if err := u.Store.FindOne(&tunnelUser, badgerhold.Where("SecretKey").Eq(secretKey)); err != nil {
191+
if errors.Is(err, badgerhold.ErrNotFound) {
192+
return nil, ErrTunnelUserNotFound
193+
}
194+
return nil, err
195+
}
196+
return &tunnelUser, nil
197+
}
198+
199+
func (u *User) ListTunnelUsers(ctx context.Context) ([]TunnelUser, error) {
200+
var tunnelUsers []TunnelUser
201+
202+
if err := u.Store.Find(&tunnelUsers, nil); err != nil {
203+
return nil, err
204+
}
205+
if tunnelUsers == nil {
206+
return []TunnelUser{}, nil
207+
}
208+
return tunnelUsers, nil
209+
}
210+
211+
func (u *User) RotateTunnelUserSecretKey(ctx context.Context, email string) (*TunnelUser, error) {
212+
tunnelUser, err := u.findTunnelUserByEmail(ctx, email)
213+
214+
if err != nil {
215+
return nil, err
216+
}
217+
218+
tunnelUser.RotateSecretKey()
219+
220+
u.Store.UpdateMatching(&TunnelUser{}, badgerhold.Where("Email").Eq(email), func(record interface{}) error {
221+
update, ok := record.(*TunnelUser)
222+
if !ok {
223+
return fmt.Errorf("error while updating superuser")
224+
}
225+
update.SecretKey = tunnelUser.SecretKey
226+
return nil
227+
})
228+
229+
return tunnelUser, nil
230+
}

internal/server/admin/users_test.go

+75
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,78 @@ func TestLogoutAdminUser(t *testing.T) {
139139
assert.Error(t, err)
140140
assert.Equal(t, ErrInvalidUserSession, err)
141141
}
142+
143+
func TestCreateTunnelUser(t *testing.T) {
144+
defer func() {
145+
store.Badger().DropAll()
146+
}()
147+
148+
ctx := context.Background()
149+
user := NewUserService(store)
150+
151+
tu, err := user.CreateTunnelUser(ctx, "test@beaver.com")
152+
assert.NoError(t, err)
153+
assert.Equal(t, "test@beaver.com", tu.Email)
154+
assert.NotEqual(t, "", tu.SecretKey)
155+
}
156+
157+
func TestGetTunnelUserBySecretKey(t *testing.T) {
158+
defer func() {
159+
store.Badger().DropAll()
160+
}()
161+
162+
ctx := context.Background()
163+
user := NewUserService(store)
164+
165+
tu, _ := user.CreateTunnelUser(ctx, "test@beaver.com")
166+
167+
ntu, err := user.GetTunnelUserBySecret(ctx, tu.SecretKey)
168+
assert.NoError(t, err)
169+
assert.Equal(t, tu.Email, ntu.Email)
170+
}
171+
172+
func TestRotateTunnelUserSecretKey(t *testing.T) {
173+
defer func() {
174+
store.Badger().DropAll()
175+
}()
176+
177+
ctx := context.Background()
178+
user := NewUserService(store)
179+
180+
tu, _ := user.CreateTunnelUser(ctx, "test@beaver.com")
181+
182+
_, err := user.RotateTunnelUserSecretKey(ctx, tu.Email)
183+
assert.NoError(t, err)
184+
185+
ntu, _ := user.findTunnelUserByEmail(ctx, "test@beaver.com")
186+
assert.NotEqual(t, tu.SecretKey, ntu.SecretKey)
187+
188+
nontu, err := user.RotateTunnelUserSecretKey(ctx, "test2@beaver.com")
189+
assert.Error(t, err)
190+
assert.Equal(t, ErrTunnelUserNotFound, err)
191+
assert.Nil(t, nontu)
192+
}
193+
194+
func TestListTunnelUsers(t *testing.T) {
195+
defer func() {
196+
store.Badger().DropAll()
197+
}()
198+
199+
ctx := context.Background()
200+
user := NewUserService(store)
201+
202+
tunnelUsers, err := user.ListTunnelUsers(ctx)
203+
assert.NoError(t, err)
204+
assert.Equal(t, 0, len(tunnelUsers))
205+
206+
_, _ = user.CreateTunnelUser(ctx, "test@beaver.com")
207+
_, _ = user.CreateTunnelUser(ctx, "test2@beaver.com")
208+
_, _ = user.CreateTunnelUser(ctx, "test3@beaver.com")
209+
210+
tunnelUsers, err = user.ListTunnelUsers(ctx)
211+
assert.NoError(t, err)
212+
assert.Equal(t, 3, len(tunnelUsers))
213+
assert.Equal(t, "test@beaver.com", tunnelUsers[0].Email)
214+
assert.Equal(t, "test2@beaver.com", tunnelUsers[1].Email)
215+
assert.Equal(t, "test3@beaver.com", tunnelUsers[2].Email)
216+
}

0 commit comments

Comments
 (0)