remove members from session.

This commit is contained in:
Miroslav Šedivý 2021-03-13 21:11:48 +01:00
parent 84f0b0aaeb
commit 3aea0c7bf5
9 changed files with 96 additions and 231 deletions

View File

@ -14,7 +14,7 @@ type MemberDataPayload struct {
func (h *MembersHandler) membersList(w http.ResponseWriter, r *http.Request) { func (h *MembersHandler) membersList(w http.ResponseWriter, r *http.Request) {
members := []MemberDataPayload{} members := []MemberDataPayload{}
for _, session := range h.sessions.Members() { for _, session := range h.sessions.List() {
profile := session.GetProfile() profile := session.GetProfile()
members = append(members, MemberDataPayload{ members = append(members, MemberDataPayload{
ID: session.ID(), ID: session.ID(),

View File

@ -30,12 +30,23 @@ func (api *ApiManagerCtx) Login(w http.ResponseWriter, r *http.Request) {
return return
} }
token := data.Secret
// TODO: Proper login. // TODO: Proper login.
//session, err := api.sessions.Authenticate(data.ID, data.Secret) session, err := api.sessions.Create(token, types.MemberProfile{
//if err != nil { Name: data.ID,
utils.HttpUnauthorized(w, "no authentication implemented") IsAdmin: true,
CanLogin: true,
CanConnect: true,
CanWatch: true,
CanHost: true,
CanAccessClipboard: true,
})
if err != nil {
utils.HttpUnauthorized(w, err)
return return
//} }
sameSite := http.SameSiteNoneMode sameSite := http.SameSiteNoneMode
if UnsecureCookies { if UnsecureCookies {
@ -43,17 +54,8 @@ func (api *ApiManagerCtx) Login(w http.ResponseWriter, r *http.Request) {
} }
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "neko-id", Name: "NEKO_SESSION",
Value: session.ID(), Value: token,
Expires: CookieExpirationDate,
Secure: !UnsecureCookies,
SameSite: sameSite,
HttpOnly: false,
})
http.SetCookie(w, &http.Cookie{
Name: "neko-secret",
Value: data.Secret,
Expires: CookieExpirationDate, Expires: CookieExpirationDate,
Secure: !UnsecureCookies, Secure: !UnsecureCookies,
SameSite: sameSite, SameSite: sameSite,
@ -68,22 +70,22 @@ func (api *ApiManagerCtx) Login(w http.ResponseWriter, r *http.Request) {
} }
func (api *ApiManagerCtx) Logout(w http.ResponseWriter, r *http.Request) { func (api *ApiManagerCtx) Logout(w http.ResponseWriter, r *http.Request) {
session := auth.GetSession(r)
// TODO: Proper logout.
err := api.sessions.Delete(session.ID())
if err != nil {
utils.HttpUnauthorized(w, err)
return
}
sameSite := http.SameSiteNoneMode sameSite := http.SameSiteNoneMode
if UnsecureCookies { if UnsecureCookies {
sameSite = http.SameSiteDefaultMode sameSite = http.SameSiteDefaultMode
} }
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "neko-id", Name: "NEKO_SESSION",
Value: "",
Expires: time.Unix(0, 0),
Secure: !UnsecureCookies,
SameSite: sameSite,
HttpOnly: false,
})
http.SetCookie(w, &http.Cookie{
Name: "neko-secret",
Value: "", Value: "",
Expires: time.Unix(0, 0), Expires: time.Unix(0, 0),
Secure: !UnsecureCookies, Secure: !UnsecureCookies,

View File

@ -6,46 +6,18 @@ import (
) )
type Session struct { type Session struct {
Password string
AdminPassword string
ImplicitHosting bool ImplicitHosting bool
DatabaseAdapter string
FilePath string
} }
func (Session) Init(cmd *cobra.Command) error { func (Session) Init(cmd *cobra.Command) error {
cmd.PersistentFlags().String("password", "neko", "password for connecting to stream")
if err := viper.BindPFlag("password", cmd.PersistentFlags().Lookup("password")); err != nil {
return err
}
cmd.PersistentFlags().String("password_admin", "admin", "admin password for connecting to stream")
if err := viper.BindPFlag("password_admin", cmd.PersistentFlags().Lookup("password_admin")); err != nil {
return err
}
cmd.PersistentFlags().Bool("implicit_hosting", true, "allow implicit control switching") cmd.PersistentFlags().Bool("implicit_hosting", true, "allow implicit control switching")
if err := viper.BindPFlag("implicit_hosting", cmd.PersistentFlags().Lookup("implicit_hosting")); err != nil { if err := viper.BindPFlag("implicit_hosting", cmd.PersistentFlags().Lookup("implicit_hosting")); err != nil {
return err return err
} }
cmd.PersistentFlags().String("database_adapter", "file", "choose database adapter for members")
if err := viper.BindPFlag("database_adapter", cmd.PersistentFlags().Lookup("database_adapter")); err != nil {
return err
}
cmd.PersistentFlags().String("file_path", "/home/neko/members.json", "file adapter: specify file path")
if err := viper.BindPFlag("file_path", cmd.PersistentFlags().Lookup("file_path")); err != nil {
return err
}
return nil return nil
} }
func (s *Session) Set() { func (s *Session) Set() {
s.Password = viper.GetString("password")
s.AdminPassword = viper.GetString("password_admin")
s.ImplicitHosting = viper.GetBool("implicit_hosting") s.ImplicitHosting = viper.GetBool("implicit_hosting")
s.DatabaseAdapter = viper.GetString("database_adapter")
s.FilePath = viper.GetString("file_path")
} }

View File

@ -24,7 +24,7 @@ func (manager *SessionManagerCtx) Authenticate(r *http.Request) (types.Session,
func getToken(r *http.Request) (string, bool) { func getToken(r *http.Request) (string, bool) {
// get from Cookie // get from Cookie
cookie, err := r.Cookie("neko-token") cookie, err := r.Cookie("NEKO_SESSION")
if err == nil { if err == nil {
return cookie.Value, true return cookie.Value, true
} }

View File

@ -9,37 +9,45 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"demodesk/neko/internal/config" "demodesk/neko/internal/config"
"demodesk/neko/internal/session/database"
"demodesk/neko/internal/types" "demodesk/neko/internal/types"
"demodesk/neko/internal/utils" "demodesk/neko/internal/utils"
) )
func New(config *config.Session) *SessionManagerCtx { func New(config *config.Session) *SessionManagerCtx {
return &SessionManagerCtx{ return &SessionManagerCtx{
logger: log.With().Str("module", "session").Logger(), logger: log.With().Str("module", "session").Logger(),
host: nil, config: config,
hostMu: sync.Mutex{}, host: nil,
config: config, hostMu: sync.Mutex{},
database: database.New(config), sessions: make(map[string]*SessionCtx),
members: make(map[string]*SessionCtx), sessionsMu: sync.Mutex{},
membersMu: sync.Mutex{}, emmiter: events.New(),
emmiter: events.New(),
} }
} }
type SessionManagerCtx struct { type SessionManagerCtx struct {
logger zerolog.Logger logger zerolog.Logger
host types.Session config *config.Session
hostMu sync.Mutex host types.Session
config *config.Session hostMu sync.Mutex
database types.MembersDatabase sessions map[string]*SessionCtx
members map[string]*SessionCtx sessionsMu sync.Mutex
membersMu sync.Mutex emmiter events.EventEmmiter
emmiter events.EventEmmiter
} }
// TODO: Extract members map + primitives. // ---
func (manager *SessionManagerCtx) add(id string, profile types.MemberProfile) types.Session { // sessions
// ---
func (manager *SessionManagerCtx) Create(id string, profile types.MemberProfile) (types.Session, error) {
manager.sessionsMu.Lock()
_, ok := manager.sessions[id]
if ok {
manager.sessionsMu.Unlock()
return nil, fmt.Errorf("Session id already exists.")
}
session := &SessionCtx{ session := &SessionCtx{
id: id, id: id,
manager: manager, manager: manager,
@ -47,104 +55,24 @@ func (manager *SessionManagerCtx) add(id string, profile types.MemberProfile) ty
profile: profile, profile: profile,
} }
manager.members[id] = session manager.sessions[id] = session
return session manager.sessionsMu.Unlock()
}
// ---
// members
// ---
func (manager *SessionManagerCtx) Connect() error {
if err := manager.database.Connect(); err != nil {
return err
}
profiles, err := manager.database.Select()
if err != nil {
return err
}
for id, profile := range profiles {
_ = manager.add(id, profile)
}
// TODO: Move to Database, or make `admin` as reserved ID.
// create default admin account at startup
_ = manager.add("admin", types.MemberProfile{
Secret: manager.config.AdminPassword,
Name: "Administrator",
IsAdmin: true,
CanLogin: true,
CanConnect: true,
CanWatch: true,
CanHost: true,
CanAccessClipboard: true,
})
// create default user account at startup
_ = manager.add("user", types.MemberProfile{
Secret: manager.config.Password,
Name: "User",
IsAdmin: false,
CanLogin: true,
CanConnect: true,
CanWatch: true,
CanHost: true,
CanAccessClipboard: true,
})
return nil
}
func (manager *SessionManagerCtx) Disconnect() error {
return manager.database.Disconnect()
}
func (manager *SessionManagerCtx) Create(id string, profile types.MemberProfile) (types.Session, error) {
manager.membersMu.Lock()
_, ok := manager.members[id]
if ok {
manager.membersMu.Unlock()
return nil, fmt.Errorf("Member ID already exists.")
}
err := manager.database.Insert(id, profile)
if err != nil {
manager.membersMu.Unlock()
return nil, err
}
session := manager.add(id, profile)
manager.membersMu.Unlock()
manager.emmiter.Emit("created", session) manager.emmiter.Emit("created", session)
return session, nil return session, nil
} }
func (manager *SessionManagerCtx) Update(id string, profile types.MemberProfile) error { func (manager *SessionManagerCtx) Update(id string, profile types.MemberProfile) error {
manager.membersMu.Lock() manager.sessionsMu.Lock()
session, ok := manager.members[id] session, ok := manager.sessions[id]
if !ok { if !ok {
manager.membersMu.Unlock() manager.sessionsMu.Unlock()
return fmt.Errorf("Member not found.") return fmt.Errorf("Session id not found.")
}
// preserve secret if not updated
if profile.Secret == "" {
profile.Secret = session.profile.Secret
}
err := manager.database.Update(id, profile)
if err != nil {
manager.membersMu.Unlock()
return err
} }
session.profile = profile session.profile = profile
manager.membersMu.Unlock() manager.sessionsMu.Unlock()
manager.emmiter.Emit("profile_changed", session) manager.emmiter.Emit("profile_changed", session)
session.profileChanged() session.profileChanged()
@ -152,24 +80,19 @@ func (manager *SessionManagerCtx) Update(id string, profile types.MemberProfile)
} }
func (manager *SessionManagerCtx) Delete(id string) error { func (manager *SessionManagerCtx) Delete(id string) error {
manager.membersMu.Lock() manager.sessionsMu.Lock()
session, ok := manager.members[id] session, ok := manager.sessions[id]
if !ok { if !ok {
manager.membersMu.Unlock() manager.sessionsMu.Unlock()
return fmt.Errorf("Member not found.") return fmt.Errorf("Session id not found.")
} }
err := manager.database.Delete(id) delete(manager.sessions, id)
if err != nil { manager.sessionsMu.Unlock()
manager.membersMu.Unlock()
return err
}
delete(manager.members, id)
manager.membersMu.Unlock()
var err error
if session.IsConnected() { if session.IsConnected() {
err = session.Disconnect("member deleted") err = session.Disconnect("session deleted")
} }
manager.emmiter.Emit("deleted", session) manager.emmiter.Emit("deleted", session)
@ -177,13 +100,25 @@ func (manager *SessionManagerCtx) Delete(id string) error {
} }
func (manager *SessionManagerCtx) Get(id string) (types.Session, bool) { func (manager *SessionManagerCtx) Get(id string) (types.Session, bool) {
manager.membersMu.Lock() manager.sessionsMu.Lock()
defer manager.membersMu.Unlock() defer manager.sessionsMu.Unlock()
session, ok := manager.members[id] session, ok := manager.sessions[id]
return session, ok return session, ok
} }
func (manager *SessionManagerCtx) List() []types.Session {
manager.sessionsMu.Lock()
defer manager.sessionsMu.Unlock()
var sessions []types.Session
for _, session := range manager.sessions {
sessions = append(sessions, session)
}
return sessions
}
// --- // ---
// host // host
// --- // ---
@ -213,39 +148,14 @@ func (manager *SessionManagerCtx) ClearHost() {
} }
// --- // ---
// members list // broadcasts
// --- // ---
func (manager *SessionManagerCtx) HasConnectedMembers() bool {
manager.membersMu.Lock()
defer manager.membersMu.Unlock()
for _, session := range manager.members {
if session.IsConnected() {
return true
}
}
return false
}
func (manager *SessionManagerCtx) Members() []types.Session {
manager.membersMu.Lock()
defer manager.membersMu.Unlock()
var sessions []types.Session
for _, session := range manager.members {
sessions = append(sessions, session)
}
return sessions
}
func (manager *SessionManagerCtx) Broadcast(v interface{}, exclude interface{}) { func (manager *SessionManagerCtx) Broadcast(v interface{}, exclude interface{}) {
manager.membersMu.Lock() manager.sessionsMu.Lock()
defer manager.membersMu.Unlock() defer manager.sessionsMu.Unlock()
for id, session := range manager.members { for id, session := range manager.sessions {
if !session.IsConnected() { if !session.IsConnected() {
continue continue
} }
@ -263,10 +173,10 @@ func (manager *SessionManagerCtx) Broadcast(v interface{}, exclude interface{})
} }
func (manager *SessionManagerCtx) AdminBroadcast(v interface{}, exclude interface{}) { func (manager *SessionManagerCtx) AdminBroadcast(v interface{}, exclude interface{}) {
manager.membersMu.Lock() manager.sessionsMu.Lock()
defer manager.membersMu.Unlock() defer manager.sessionsMu.Unlock()
for id, session := range manager.members { for id, session := range manager.sessions {
if !session.IsConnected() || !session.IsAdmin() { if !session.IsConnected() || !session.IsAdmin() {
continue continue
} }

View File

@ -26,10 +26,6 @@ func (session *SessionCtx) ID() string {
// profile // profile
// --- // ---
func (session *SessionCtx) VerifySecret(secret string) bool {
return session.profile.Secret == secret
}
func (session *SessionCtx) Name() string { func (session *SessionCtx) Name() string {
return session.profile.Name return session.profile.Name
} }

View File

@ -32,7 +32,6 @@ type Session interface {
ID() string ID() string
// profile // profile
VerifySecret(secret string) bool
Name() string Name() string
IsAdmin() bool IsAdmin() bool
CanLogin() bool CanLogin() bool
@ -60,20 +59,16 @@ type Session interface {
} }
type SessionManager interface { type SessionManager interface {
Connect() error
Disconnect() error
Create(id string, profile MemberProfile) (Session, error) Create(id string, profile MemberProfile) (Session, error)
Update(id string, profile MemberProfile) error Update(id string, profile MemberProfile) error
Get(id string) (Session, bool) Get(id string) (Session, bool)
Delete(id string) error Delete(id string) error
List() []Session
SetHost(host Session) SetHost(host Session)
GetHost() Session GetHost() Session
ClearHost() ClearHost()
HasConnectedMembers() bool
Members() []Session
Broadcast(v interface{}, exclude interface{}) Broadcast(v interface{}, exclude interface{})
AdminBroadcast(v interface{}, exclude interface{}) AdminBroadcast(v interface{}, exclude interface{})
@ -88,6 +83,5 @@ type SessionManager interface {
ImplicitHosting() bool ImplicitHosting() bool
AuthenticateRequest(r *http.Request) (Session, error) Authenticate(r *http.Request) (Session, error)
Authenticate(id string, secret string) (Session, error)
} }

View File

@ -24,7 +24,7 @@ func (h *MessageHandlerCtx) systemInit(session types.Session) error {
} }
members := map[string]message.MemberData{} members := map[string]message.MemberData{}
for _, session := range h.sessions.Members() { for _, session := range h.sessions.List() {
members[session.ID()] = message.MemberData{ members[session.ID()] = message.MemberData{
ID: session.ID(), ID: session.ID(),
Profile: session.GetProfile(), Profile: session.GetProfile(),

View File

@ -132,9 +132,6 @@ func (neko *Neko) Start() {
neko.sessionManager = session.New( neko.sessionManager = session.New(
neko.Configs.Session, neko.Configs.Session,
) )
if err := neko.sessionManager.Connect(); err != nil {
neko.logger.Panic().Err(err).Msg("unable to connect to session manager")
}
neko.desktopManager = desktop.New( neko.desktopManager = desktop.New(
neko.Configs.Desktop, neko.Configs.Desktop,
@ -184,12 +181,6 @@ func (neko *Neko) Start() {
} }
func (neko *Neko) Shutdown() { func (neko *Neko) Shutdown() {
if err := neko.sessionManager.Disconnect(); err != nil {
neko.logger.Err(err).Msg("session manager disconnect with an error")
} else {
neko.logger.Debug().Msg("session manager disconnect")
}
if err := neko.desktopManager.Shutdown(); err != nil { if err := neko.desktopManager.Shutdown(); err != nil {
neko.logger.Err(err).Msg("desktop manager shutdown with an error") neko.logger.Err(err).Msg("desktop manager shutdown with an error")
} else { } else {