diff --git a/internal/api/router.go b/internal/api/router.go index 04c3993a..33bdb1d6 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -36,27 +36,30 @@ func New( } func (api *ApiManagerCtx) Route(r chi.Router) { - r.Use(api.Authenticate) + r.Post("/login", api.Login) - membersHandler := members.New(api.sessions) - r.Route("/members", membersHandler.Route) + // Authenticated area + r.Group(func(r chi.Router) { + r.Use(api.Authenticate) - roomHandler := room.New(api.sessions, api.desktop, api.capture) - r.Route("/room", roomHandler.Route) + r.Post("/logout", api.Logout) + r.Get("/whoami", api.Whoami) - r.Get("/test", func(w http.ResponseWriter, r *http.Request) { - session := auth.GetSession(r) - utils.HttpBadRequest(w, "Hi `" + session.ID() + "`, you are authenticated.") + membersHandler := members.New(api.sessions) + r.Route("/members", membersHandler.Route) + + roomHandler := room.New(api.sessions, api.desktop, api.capture) + r.Route("/room", roomHandler.Route) + + for path, router := range api.routers { + r.Route(path, router) + } }) - - for path, router := range api.routers { - r.Route(path, router) - } } func (api *ApiManagerCtx) Authenticate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - session, err := api.sessions.Authenticate(r) + session, err := api.sessions.AuthenticateRequest(r) if err != nil { utils.HttpUnauthorized(w, err) } else { diff --git a/internal/api/session.go b/internal/api/session.go new file mode 100644 index 00000000..fbc763cf --- /dev/null +++ b/internal/api/session.go @@ -0,0 +1,80 @@ +package api + +import ( + "time" + "net/http" + + "demodesk/neko/internal/utils" + "demodesk/neko/internal/types" + "demodesk/neko/internal/http/auth" +) + +type SessionLoginPayload struct { + ID string `json:"id"` + Secret string `json:"secret"` +} + +type SessionWhoamiPayload struct { + ID string `json:"id"` + Profile types.MemberProfile `json:"profile"` + State types.MemberState `json:"state"` +} + +func (api *ApiManagerCtx) Login(w http.ResponseWriter, r *http.Request) { + data := &SessionLoginPayload{} + if !utils.HttpJsonRequest(w, r, data) { + return + } + + session, err := api.sessions.Authenticate(data.ID, data.Secret) + if err != nil { + utils.HttpUnauthorized(w, err) + return + } + + http.SetCookie(w, &http.Cookie{ + Name: "neko-id", + Value: session.ID(), + Expires: time.Now().Add(365 * 24 * time.Hour), + HttpOnly: false, + }) + + http.SetCookie(w, &http.Cookie{ + Name: "neko-secret", + Value: data.Secret, + Expires: time.Now().Add(365 * 24 * time.Hour), + HttpOnly: true, + }) + + utils.HttpSuccess(w) +} + +func (api *ApiManagerCtx) Logout(w http.ResponseWriter, r *http.Request) { + http.SetCookie(w, &http.Cookie{ + Name: "neko-id", + Value: "", + Path: "/", + Expires: time.Unix(0, 0), + HttpOnly: false, + }) + + http.SetCookie(w, &http.Cookie{ + Name: "neko-secret", + Value: "", + Path: "/", + Expires: time.Unix(0, 0), + HttpOnly: true, + }) + + utils.HttpSuccess(w) +} + +func (api *ApiManagerCtx) Whoami(w http.ResponseWriter, r *http.Request) { + session := auth.GetSession(r) + + utils.HttpSuccess(w, SessionWhoamiPayload{ + ID: session.ID(), + Profile: session.GetProfile(), + State: session.GetState(), + }) +} diff --git a/internal/session/auth.go b/internal/session/auth.go index 2554b178..2c9be576 100644 --- a/internal/session/auth.go +++ b/internal/session/auth.go @@ -7,12 +7,16 @@ import ( "demodesk/neko/internal/types" ) -func (manager *SessionManagerCtx) Authenticate(r *http.Request) (types.Session, error) { +func (manager *SessionManagerCtx) AuthenticateRequest(r *http.Request) (types.Session, error) { id, secret, ok := getAuthData(r) if !ok { return nil, fmt.Errorf("no authentication provided") } + return manager.Authenticate(id, secret) +} + +func (manager *SessionManagerCtx) Authenticate(id string, secret string) (types.Session, error) { session, ok := manager.Get(id) if !ok { return nil, fmt.Errorf("member not found") @@ -30,14 +34,22 @@ func (manager *SessionManagerCtx) Authenticate(r *http.Request) (types.Session, } func getAuthData(r *http.Request) (string, string, bool) { + // get from Cookies + cid, err1 := r.Cookie("neko-id") + csecret, err2 := r.Cookie("neko-secret") + if err1 == nil && err2 == nil { + return cid.Value, csecret.Value, true + } + + // get from BasicAuth id, secret, ok := r.BasicAuth() if ok { return id, secret, true } + // get from QueryParams id = r.URL.Query().Get("id") secret = r.URL.Query().Get("secret") - if id != "" && secret != "" { return id, secret, true } diff --git a/internal/types/session.go b/internal/types/session.go index 341f31cc..ae6494b9 100644 --- a/internal/types/session.go +++ b/internal/types/session.go @@ -89,5 +89,6 @@ type SessionManager interface { ImplicitHosting() bool - Authenticate(r *http.Request) (Session, error) + AuthenticateRequest(r *http.Request) (Session, error) + Authenticate(id string, secret string) (Session, error) } diff --git a/internal/websocket/manager.go b/internal/websocket/manager.go index 9b218ab3..c764f73b 100644 --- a/internal/websocket/manager.go +++ b/internal/websocket/manager.go @@ -155,7 +155,7 @@ func (ws *WebSocketManagerCtx) Upgrade(w http.ResponseWriter, r *http.Request, c return err } - session, err := ws.sessions.Authenticate(r) + session, err := ws.sessions.AuthenticateRequest(r) if err != nil { ws.logger.Warn().Err(err).Msg("authentication failed")