Skip to content

Commit 3355234

Browse files
committed
feat(ds-host): access user and admin routes from tsnet server
- add fromtsnet router - add mustBeAdmin middleware with tests
1 parent 41eaf95 commit 3355234

5 files changed

Lines changed: 151 additions & 22 deletions

File tree

cmd/ds-host/ds-host.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,11 @@ func main() {
539539
UserRoutes: userRoutes}
540540
userFromPublic.Init()
541541

542+
userFromTSNet := &userroutes.FromTSNet{
543+
UserModel: userModel,
544+
UserRoutes: userRoutes}
545+
userFromTSNet.Init()
546+
542547
dropserverRoutes := &appspacerouter.DropserverRoutes{
543548
V0DropServerRoutes: &appspacerouter.V0DropserverRoutes{
544549
AppspaceModel: appspaceModel,
@@ -588,9 +593,9 @@ func main() {
588593
AppspaceRouter: appspaceFromPublic}
589594

590595
userTSNet := &server.UserTSNet{
591-
Config: runtimeConfig,
592-
SettingsModel: settingsModel,
593-
//TODO UserRoutes: ,
596+
Config: runtimeConfig,
597+
SettingsModel: settingsModel,
598+
UserRoutes: userFromTSNet,
594599
TSNetStatusEvents: userTSNetEvents,
595600
TSNetPeersEvents: userTSNetPeersEvents,
596601
}

cmd/ds-host/server/tsnetnode.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@ func (n *TSNetNode) startStopHTTPS() {
301301
func (n *TSNetNode) handler(ln net.Listener) {
302302
err := http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
303303
loggerFn := n.getLogger("http.Serve").Clone
304-
loggerFn().Debug("tsnet got request")
305304

306305
status := n.getStatus()
307306
if !status.Usable {

cmd/ds-host/userroutes/adminroutes.go

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type AdminRoutes struct {
5151
func (a *AdminRoutes) subRouter() http.Handler {
5252
r := chi.NewRouter()
5353

54-
// TODO admin-only middleware
54+
r.Use(a.mustBeAdmin)
5555

5656
r.Get("/user/", a.getUsers)
5757
r.Post("/user/", a.postUser)
@@ -71,23 +71,20 @@ func (a *AdminRoutes) subRouter() http.Handler {
7171
return r
7272
}
7373

74-
// TODO: do this next
75-
// func mustBeAdmin(next http.Handler) http.Handler {
76-
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
77-
// _, ok := domain.CtxAuthUserID(r.Context())
78-
// if !ok {
79-
// // TODO: only do this when request is for an html page.
80-
// if strings.HasPrefix(r.URL.Path, "/api") {
81-
// w.WriteHeader(http.StatusUnauthorized)
82-
// } else {
83-
// http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
84-
// }
85-
// return
86-
// }
87-
88-
// next.ServeHTTP(w, r)
89-
// })
90-
// }
74+
func (a *AdminRoutes) mustBeAdmin(next http.Handler) http.Handler {
75+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76+
userID, ok := domain.CtxAuthUserID(r.Context())
77+
if !ok {
78+
w.WriteHeader(http.StatusUnauthorized)
79+
return
80+
}
81+
if !a.UserModel.IsAdmin(userID) {
82+
w.WriteHeader(http.StatusForbidden)
83+
return
84+
}
85+
next.ServeHTTP(w, r)
86+
})
87+
}
9188

9289
// getUsers returns the list of users on the system
9390
func (a *AdminRoutes) getUsers(w http.ResponseWriter, r *http.Request) {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package userroutes
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
8+
"github.com/go-chi/chi/v5"
9+
"github.com/golang/mock/gomock"
10+
"github.com/teleclimber/DropServer/cmd/ds-host/domain"
11+
"github.com/teleclimber/DropServer/cmd/ds-host/testmocks"
12+
)
13+
14+
func TestMustBeAdminNoAuth(t *testing.T) {
15+
a := AdminRoutes{}
16+
router := chi.NewMux()
17+
router.Use(a.mustBeAdmin)
18+
router.Get("/", func(w http.ResponseWriter, r *http.Request) {})
19+
20+
req, err := http.NewRequest(http.MethodGet, "/", nil)
21+
if err != nil {
22+
t.Fatal(err)
23+
}
24+
25+
rr := httptest.NewRecorder()
26+
router.ServeHTTP(rr, req)
27+
28+
if rr.Result().StatusCode != http.StatusUnauthorized {
29+
t.Errorf("expected Unauthorized got status %v", rr.Result().Status)
30+
}
31+
}
32+
33+
func TestMustBeAdminForbidden(t *testing.T) {
34+
mockCtrl := gomock.NewController(t)
35+
defer mockCtrl.Finish()
36+
37+
reqUid := domain.UserID(7)
38+
39+
um := testmocks.NewMockUserModel(mockCtrl)
40+
um.EXPECT().IsAdmin(reqUid).Return(false)
41+
a := AdminRoutes{
42+
UserModel: um}
43+
44+
router := chi.NewMux()
45+
router.Use(a.mustBeAdmin)
46+
router.Get("/", func(w http.ResponseWriter, r *http.Request) {})
47+
48+
req, err := http.NewRequest(http.MethodGet, "/", nil)
49+
if err != nil {
50+
t.Fatal(err)
51+
}
52+
req = req.WithContext(domain.CtxWithAuthUserID(req.Context(), reqUid))
53+
54+
rr := httptest.NewRecorder()
55+
router.ServeHTTP(rr, req)
56+
57+
if rr.Result().StatusCode != http.StatusForbidden {
58+
t.Errorf("expected Forbidden got status %v", rr.Result().Status)
59+
}
60+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package userroutes
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
7+
"github.com/go-chi/chi/v5"
8+
"github.com/teleclimber/DropServer/cmd/ds-host/domain"
9+
"github.com/teleclimber/DropServer/cmd/ds-host/record"
10+
)
11+
12+
type FromTSNet struct {
13+
UserModel interface {
14+
GetFromTSNet(string) (domain.User, error)
15+
} `checkinject:"required"`
16+
UserRoutes interface {
17+
BuildRoutes(mux *chi.Mux)
18+
} `checkinject:"required"`
19+
20+
mux *chi.Mux
21+
}
22+
23+
func (f *FromTSNet) Init() {
24+
r := chi.NewRouter()
25+
r.Use(addCSPHeaders)
26+
r.Use(f.accountUser)
27+
f.UserRoutes.BuildRoutes(r)
28+
f.mux = r
29+
}
30+
31+
func (f *FromTSNet) ServeHTTP(res http.ResponseWriter, req *http.Request) {
32+
f.mux.ServeHTTP(res, req)
33+
}
34+
35+
// AccountUser middleware sets the user id in context
36+
// from the tsnet user data.
37+
func (f *FromTSNet) accountUser(next http.Handler) http.Handler {
38+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39+
ctx := r.Context()
40+
tsUserID, ok := domain.CtxTSNetUserID(ctx)
41+
if !ok {
42+
f.getLogger("accountUser").Error(errors.New("accountUser() no tsnet user id"))
43+
w.WriteHeader(http.StatusUnauthorized)
44+
return
45+
}
46+
47+
u, err := f.UserModel.GetFromTSNet(tsUserID)
48+
if err == domain.ErrNoRowsInResultSet {
49+
f.getLogger("accountUser").Debug("GetFromTSNet() no sql rows for tsnetid")
50+
w.WriteHeader(http.StatusUnauthorized)
51+
return
52+
}
53+
if err != nil {
54+
f.getLogger("accountUser").AddNote("GetFromTSNet").Error(err)
55+
w.WriteHeader(http.StatusInternalServerError)
56+
return
57+
}
58+
59+
ctx = domain.CtxWithAuthUserID(ctx, u.UserID)
60+
r = r.WithContext(ctx)
61+
62+
next.ServeHTTP(w, r)
63+
})
64+
}
65+
66+
func (f *FromTSNet) getLogger(note string) *record.DsLogger {
67+
return record.NewDsLogger().AddNote("userroutes FromTSNet").AddNote(note)
68+
}

0 commit comments

Comments
 (0)