Skip to content

Commit 881833d

Browse files
wuriyantowuriyanto
authored andcommitted
feat: dashboard auth
1 parent b9e14f9 commit 881833d

16 files changed

Lines changed: 803 additions & 55 deletions

File tree

dashboard/handler/api.go

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ package handler
33
import (
44
"encoding/json"
55
"errors"
6+
"fmt"
67
"log"
78
"net/http"
89
"regexp"
910
"strings"
11+
"time"
1012

1113
"github.com/telkomdev/tob/config"
1214
"github.com/telkomdev/tob/dashboard/shared"
15+
"github.com/telkomdev/tob/dashboard/utils"
1316
)
1417

1518
var (
@@ -21,12 +24,20 @@ type WebhookMessage struct {
2124
Message string `json:"message"`
2225
}
2326

27+
// LoginPayload type
28+
type LoginPayload struct {
29+
Username string `json:"username"`
30+
Password string `json:"password"`
31+
}
32+
2433
// DashboardHTTPHandler type
2534
type DashboardHTTPHandler struct {
26-
serviceData map[string]map[string]interface{}
27-
logger *log.Logger
28-
webhookTobTokens []string
29-
dashboardTitle string
35+
serviceData map[string]map[string]interface{}
36+
logger *log.Logger
37+
webhookTobTokens []string
38+
dashboardTitle string
39+
dashboardUsername string
40+
dashboardPassword string
3041
}
3142

3243
// Data type
@@ -35,6 +46,12 @@ type Data struct {
3546
DashboardTitle string `json:"dashboardTitle"`
3647
}
3748

49+
// LoginResponse type
50+
type LoginResponse struct {
51+
Username string `json:"username"`
52+
JWTString string `json:"jwtString"`
53+
}
54+
3855
// NewDashboardHTTPHandler DashboardHTTPHandler's constructor
3956
func NewDashboardHTTPHandler(tobConfig config.Config, logger *log.Logger) (*DashboardHTTPHandler, error) {
4057
if dashboardTitle, ok := tobConfig["dashboardTitle"].(string); ok {
@@ -102,14 +119,104 @@ func NewDashboardHTTPHandler(tobConfig config.Config, logger *log.Logger) (*Dash
102119
serviceData[name] = services
103120
}
104121

122+
dashboardUsername, ok := tobConfig["dashboardUsername"].(string)
123+
if !ok {
124+
return nil, errors.New("cannot parse dashboardUsername from configs")
125+
}
126+
127+
dashboardPassword, ok := tobConfig["dashboardPassword"].(string)
128+
if !ok {
129+
return nil, errors.New("cannot parse dashboardPassword from configs")
130+
}
131+
105132
return &DashboardHTTPHandler{
106-
dashboardTitle: defaultDashboardTitle,
107-
serviceData: serviceData,
108-
logger: logger,
109-
webhookTobTokens: webhookTobTokens,
133+
dashboardTitle: defaultDashboardTitle,
134+
serviceData: serviceData,
135+
logger: logger,
136+
webhookTobTokens: webhookTobTokens,
137+
dashboardUsername: dashboardUsername,
138+
dashboardPassword: dashboardPassword,
110139
}, nil
111140
}
112141

142+
// Login will handle user login
143+
func (h *DashboardHTTPHandler) Login(jwtService utils.JwtService) http.HandlerFunc {
144+
return func(resp http.ResponseWriter, req *http.Request) {
145+
146+
if req.Method != http.MethodPost {
147+
shared.BuildJSONResponse(resp, shared.Response[shared.EmptyJSON]{
148+
Success: false,
149+
Code: 405,
150+
Message: "http method not valid",
151+
Data: shared.EmptyJSON{},
152+
}, 405)
153+
return
154+
}
155+
156+
var loginPayload LoginPayload
157+
158+
err := json.NewDecoder(req.Body).Decode(&loginPayload)
159+
if err != nil {
160+
shared.BuildJSONResponse(resp, shared.Response[shared.EmptyJSON]{
161+
Success: false,
162+
Code: 400,
163+
Message: "login payload is not valid",
164+
Data: shared.EmptyJSON{},
165+
}, 400)
166+
return
167+
}
168+
169+
hashedPassword, err := utils.Sha256Hex([]byte(loginPayload.Password))
170+
if err != nil {
171+
shared.BuildJSONResponse(resp, shared.Response[shared.EmptyJSON]{
172+
Success: false,
173+
Code: 400,
174+
Message: "login payload is not valid",
175+
Data: shared.EmptyJSON{},
176+
}, 400)
177+
return
178+
}
179+
180+
if h.dashboardUsername != loginPayload.Username || h.dashboardPassword != hashedPassword {
181+
shared.BuildJSONResponse(resp, shared.Response[shared.EmptyJSON]{
182+
Success: false,
183+
Code: 401,
184+
Message: "username or password is not valid",
185+
Data: shared.EmptyJSON{},
186+
}, 401)
187+
return
188+
}
189+
190+
var claim utils.Claim
191+
claim.Alg = utils.HS256
192+
claim.Subject = h.dashboardUsername
193+
claim.User.ID = h.dashboardUsername
194+
claim.User.FullName = h.dashboardUsername
195+
claim.User.Email = h.dashboardUsername
196+
197+
jwtString, err := jwtService.Generate(&claim, time.Hour*8766)
198+
if err != nil {
199+
shared.BuildJSONResponse(resp, shared.Response[shared.EmptyJSON]{
200+
Success: false,
201+
Code: 401,
202+
Message: "error generating jwt",
203+
Data: shared.EmptyJSON{},
204+
}, 401)
205+
return
206+
}
207+
208+
shared.BuildJSONResponse(resp, shared.Response[LoginResponse]{
209+
Success: true,
210+
Code: 200,
211+
Message: "login succeed",
212+
Data: LoginResponse{
213+
Username: h.dashboardUsername,
214+
JWTString: fmt.Sprintf("Bearer %s", jwtString),
215+
},
216+
}, 200)
217+
}
218+
}
219+
113220
// GetServices will return tob services
114221
func (h *DashboardHTTPHandler) GetServices() http.HandlerFunc {
115222
return func(resp http.ResponseWriter, req *http.Request) {

dashboard/middleware/jwt.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package middleware
2+
3+
import (
4+
"net/http"
5+
"strings"
6+
7+
"github.com/telkomdev/tob/dashboard/shared"
8+
"github.com/telkomdev/tob/dashboard/utils"
9+
)
10+
11+
// JWTMiddleware this middleware function for verifying accessToken from Authorization Header
12+
func JWTMiddleware(jwtService utils.JwtService, next http.Handler) http.Handler {
13+
14+
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
15+
accessToken := req.Header.Get("Authorization")
16+
if accessToken == "" {
17+
shared.BuildJSONResponse(res, shared.Response[shared.EmptyJSON]{
18+
Success: false,
19+
Code: 401,
20+
Message: "no token provided",
21+
Data: shared.EmptyJSON{},
22+
}, 401)
23+
return
24+
}
25+
tokenSlice := strings.Split(accessToken, " ")
26+
if len(tokenSlice) < 2 {
27+
shared.BuildJSONResponse(res, shared.Response[shared.EmptyJSON]{
28+
Success: false,
29+
Code: 401,
30+
Message: "token is not valid",
31+
Data: shared.EmptyJSON{},
32+
}, 401)
33+
return
34+
}
35+
36+
if tokenSlice[0] != "Bearer" {
37+
shared.BuildJSONResponse(res, shared.Response[shared.EmptyJSON]{
38+
Success: false,
39+
Code: 401,
40+
Message: "token is not valid",
41+
Data: shared.EmptyJSON{},
42+
}, 401)
43+
return
44+
}
45+
tokenString := tokenSlice[1]
46+
claim, err := jwtService.Validate(utils.HS256, tokenString)
47+
if err != nil {
48+
shared.BuildJSONResponse(res, shared.Response[shared.EmptyJSON]{
49+
Success: false,
50+
Code: 401,
51+
Message: err.Error(),
52+
Data: shared.EmptyJSON{},
53+
}, 401)
54+
return
55+
}
56+
57+
userID := claim.Subject
58+
req.Header.Add("userId", userID)
59+
next.ServeHTTP(res, req)
60+
})
61+
}

dashboard/server/http_server.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package server
22

33
import (
4+
"errors"
45
"fmt"
56
"io/fs"
67
"log"
78
"net/http"
9+
"os"
10+
"path"
11+
"strings"
812

913
"github.com/telkomdev/tob/config"
1014
"github.com/telkomdev/tob/dashboard/handler"
15+
"github.com/telkomdev/tob/dashboard/middleware"
1116
"github.com/telkomdev/tob/dashboard/ui"
17+
"github.com/telkomdev/tob/dashboard/utils"
1218
)
1319

1420
var (
@@ -21,6 +27,8 @@ type HTTPServer struct {
2127
logger *log.Logger
2228
configs config.Config
2329

30+
jwtService utils.JwtService
31+
2432
dashboardStaticAssets fs.FS
2533
dashboardHTTPHandler *handler.DashboardHTTPHandler
2634
}
@@ -31,6 +39,13 @@ func NewHTTPServer(configs config.Config, logger *log.Logger) (*HTTPServer, erro
3139
defaultDashboardHTTPPort = int(parsedDashboardHTTPPort)
3240
}
3341

42+
dashboardJwtKey, ok := configs["dashboardJwtKey"].(string)
43+
if !ok {
44+
return nil, errors.New("cannot parse dashboardJwtKey from configs")
45+
}
46+
47+
jwtService := utils.NewJWT(dashboardJwtKey)
48+
3449
dashboardStaticAssets, err := ui.Assets()
3550
if err != nil {
3651
return nil, err
@@ -47,6 +62,7 @@ func NewHTTPServer(configs config.Config, logger *log.Logger) (*HTTPServer, erro
4762
logger: logger,
4863
dashboardStaticAssets: dashboardStaticAssets,
4964
dashboardHTTPHandler: dashboardHTTPHandler,
65+
jwtService: jwtService,
5066
}, nil
5167
}
5268

@@ -55,9 +71,31 @@ func (s *HTTPServer) Run() {
5571
mux := http.NewServeMux()
5672

5773
dashboardFs := http.FileServer(http.FS(s.dashboardStaticAssets))
58-
mux.Handle("/", http.StripPrefix("/", dashboardFs))
5974

60-
mux.HandleFunc("/api/services", s.dashboardHTTPHandler.GetServices())
75+
var index = func() http.Handler {
76+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
77+
if r.URL.Path == "/" {
78+
dashboardFs.ServeHTTP(w, r)
79+
return
80+
}
81+
82+
f, err := s.dashboardStaticAssets.Open(strings.TrimPrefix(path.Clean(r.URL.Path), "/"))
83+
if err == nil {
84+
defer f.Close()
85+
}
86+
if os.IsNotExist(err) {
87+
r.URL.Path = "/"
88+
}
89+
90+
dashboardFs.ServeHTTP(w, r)
91+
})
92+
}
93+
94+
// mux.Handle("/", http.StripPrefix("/", dashboardFs))
95+
mux.Handle("/", index())
96+
97+
mux.HandleFunc("/api/login", s.dashboardHTTPHandler.Login(s.jwtService))
98+
mux.Handle("/api/services", middleware.JWTMiddleware(s.jwtService, s.dashboardHTTPHandler.GetServices()))
6199
mux.HandleFunc("/api/tob/webhook", s.dashboardHTTPHandler.HandleTobWebhook())
62100

63101
log.Printf("Dashboard HTTP server running on port %d\n", s.port)

0 commit comments

Comments
 (0)