1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2// See LICENSE.txt for license information.
3
4package app
5
6import (
7	"net/http"
8
9	"github.com/mattermost/mattermost-server/v6/model"
10	"github.com/mattermost/mattermost-server/v6/shared/i18n"
11	"github.com/mattermost/mattermost-server/v6/shared/mlog"
12)
13
14type webSocketHandler interface {
15	ServeWebSocket(*WebConn, *model.WebSocketRequest)
16}
17
18type WebSocketRouter struct {
19	app      *App
20	handlers map[string]webSocketHandler
21}
22
23func (wr *WebSocketRouter) Handle(action string, handler webSocketHandler) {
24	wr.handlers[action] = handler
25}
26
27func (wr *WebSocketRouter) ServeWebSocket(conn *WebConn, r *model.WebSocketRequest) {
28	if r.Action == "" {
29		err := model.NewAppError("ServeWebSocket", "api.web_socket_router.no_action.app_error", nil, "", http.StatusBadRequest)
30		returnWebSocketError(wr.app, conn, r, err)
31		return
32	}
33
34	if r.Seq <= 0 {
35		err := model.NewAppError("ServeWebSocket", "api.web_socket_router.bad_seq.app_error", nil, "", http.StatusBadRequest)
36		returnWebSocketError(wr.app, conn, r, err)
37		return
38	}
39
40	if r.Action == model.WebsocketAuthenticationChallenge {
41		if conn.GetSessionToken() != "" {
42			return
43		}
44
45		token, ok := r.Data["token"].(string)
46		if !ok {
47			conn.WebSocket.Close()
48			return
49		}
50
51		session, err := wr.app.GetSession(token)
52		if err != nil {
53			conn.WebSocket.Close()
54			return
55		}
56		conn.SetSession(session)
57		conn.SetSessionToken(session.Token)
58		conn.UserId = session.UserId
59
60		// TODO: Same logic to reconnect queue as api4/websocket.go
61
62		wr.app.HubRegister(conn)
63
64		wr.app.Srv().Go(func() {
65			wr.app.SetStatusOnline(session.UserId, false)
66			wr.app.UpdateLastActivityAtIfNeeded(*session)
67		})
68
69		resp := model.NewWebSocketResponse(model.StatusOk, r.Seq, nil)
70		hub := wr.app.GetHubForUserId(conn.UserId)
71		if hub == nil {
72			return
73		}
74		hub.SendMessage(conn, resp)
75
76		return
77	}
78
79	if !conn.IsAuthenticated() {
80		err := model.NewAppError("ServeWebSocket", "api.web_socket_router.not_authenticated.app_error", nil, "", http.StatusUnauthorized)
81		returnWebSocketError(wr.app, conn, r, err)
82		return
83	}
84
85	handler, ok := wr.handlers[r.Action]
86	if !ok {
87		err := model.NewAppError("ServeWebSocket", "api.web_socket_router.bad_action.app_error", nil, "", http.StatusInternalServerError)
88		returnWebSocketError(wr.app, conn, r, err)
89		return
90	}
91
92	handler.ServeWebSocket(conn, r)
93}
94
95func returnWebSocketError(app *App, conn *WebConn, r *model.WebSocketRequest, err *model.AppError) {
96	logF := mlog.Error
97	if err.StatusCode >= http.StatusBadRequest && err.StatusCode < http.StatusInternalServerError {
98		logF = mlog.Debug
99	}
100	logF(
101		"websocket routing error.",
102		mlog.Int64("seq", r.Seq),
103		mlog.String("user_id", conn.UserId),
104		mlog.String("system_message", err.SystemMessage(i18n.T)),
105		mlog.Err(err),
106	)
107
108	hub := app.GetHubForUserId(conn.UserId)
109	if hub == nil {
110		return
111	}
112
113	err.DetailedError = ""
114	errorResp := model.NewWebSocketError(r.Seq, err)
115	hub.SendMessage(conn, errorResp)
116}
117