diff --git a/dev/runtime/config.yml b/dev/runtime/config.yml index 21fc2968..ec2ca1c7 100644 --- a/dev/runtime/config.yml +++ b/dev/runtime/config.yml @@ -65,11 +65,41 @@ desktop: member: provider: "object" object: - admin_password: "admin" - user_password: "neko" + users: + - username: "admin" + password: "admin" + profile: + name: "Administrator" + is_admin: true + can_login: true + can_connect: true + can_watch: true + can_host: true + can_share_media: true + can_access_clipboard: true + sends_inactive_cursor: true + can_see_inactive_cursors: true + - username: "user" + password: "neko" + profile: + name: "User" + is_admin: false + can_login: true + can_connect: true + can_watch: true + can_host: true + can_share_media: true + can_access_clipboard: true + sends_inactive_cursor: true + can_see_inactive_cursors: false # provider: "file" # file: # path: "/home/neko/members.json" + # provider: "multiuser" + # multiuser: + # admin_password: "admin" + # user_password: "neko" + # provider: "noauth" session: # Allows reconnecting the websocket even if the previous diff --git a/internal/config/member.go b/internal/config/member.go index 2afeb7ae..b12db884 100644 --- a/internal/config/member.go +++ b/internal/config/member.go @@ -1,12 +1,14 @@ package config import ( + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/demodesk/neko/internal/member/file" "github.com/demodesk/neko/internal/member/multiuser" "github.com/demodesk/neko/internal/member/object" + "github.com/demodesk/neko/pkg/utils" ) type Member struct { @@ -31,13 +33,8 @@ func (Member) Init(cmd *cobra.Command) error { } // object provider - cmd.PersistentFlags().String("member.object.user_password", "", "member object provider: user password") - if err := viper.BindPFlag("member.object.user_password", cmd.PersistentFlags().Lookup("member.object.user_password")); err != nil { - return err - } - - cmd.PersistentFlags().String("member.object.admin_password", "", "member object provider: admin password") - if err := viper.BindPFlag("member.object.admin_password", cmd.PersistentFlags().Lookup("member.object.admin_password")); err != nil { + cmd.PersistentFlags().String("member.object.users", "[]", "member object provider: users in JSON format") + if err := viper.BindPFlag("member.object.users", cmd.PersistentFlags().Lookup("member.object.users")); err != nil { return err } @@ -62,8 +59,11 @@ func (s *Member) Set() { s.File.Path = viper.GetString("member.file.path") // object provider - s.Object.UserPassword = viper.GetString("member.object.user_password") - s.Object.AdminPassword = viper.GetString("member.object.admin_password") + if err := viper.UnmarshalKey("member.object.users", &s.Object.Users, viper.DecodeHook( + utils.JsonStringAutoDecode(s.Object.Users), + )); err != nil { + log.Warn().Err(err).Msgf("unable to parse member object users") + } // multiuser provider s.Multiuser.UserPassword = viper.GetString("member.multiuser.user_password") diff --git a/internal/member/object/provider.go b/internal/member/object/provider.go index 7410ff39..5e93500e 100644 --- a/internal/member/object/provider.go +++ b/internal/member/object/provider.go @@ -7,48 +7,20 @@ import ( func New(config Config) types.MemberProvider { return &MemberProviderCtx{ config: config, - entries: make(map[string]*MemberEntry), + entries: make(map[string]*memberEntry), } } type MemberProviderCtx struct { config Config - entries map[string]*MemberEntry + entries map[string]*memberEntry } func (provider *MemberProviderCtx) Connect() error { var err error - if provider.config.AdminPassword != "" { - // create default admin account at startup - _, err = provider.Insert("admin", provider.config.AdminPassword, types.MemberProfile{ - Name: "Administrator", - IsAdmin: true, - CanLogin: true, - CanConnect: true, - CanWatch: true, - CanHost: true, - CanShareMedia: true, - CanAccessClipboard: true, - SendsInactiveCursor: true, - CanSeeInactiveCursors: true, - }) - } - - if provider.config.UserPassword != "" { - // create default user account at startup - _, err = provider.Insert("user", provider.config.UserPassword, types.MemberProfile{ - Name: "User", - IsAdmin: false, - CanLogin: true, - CanConnect: true, - CanWatch: true, - CanHost: true, - CanShareMedia: true, - CanAccessClipboard: true, - SendsInactiveCursor: true, - CanSeeInactiveCursors: false, - }) + for _, entry := range provider.config.Users { + _, err = provider.Insert(entry.Username, entry.Password, entry.Profile) } return err @@ -68,11 +40,11 @@ func (provider *MemberProviderCtx) Authenticate(username string, password string } // TODO: Use hash function. - if entry.Password != password { + if !entry.CheckPassword(password) { return "", types.MemberProfile{}, types.ErrMemberInvalidPassword } - return id, entry.Profile, nil + return id, entry.profile, nil } func (provider *MemberProviderCtx) Insert(username string, password string, profile types.MemberProfile) (string, error) { @@ -84,10 +56,10 @@ func (provider *MemberProviderCtx) Insert(username string, password string, prof return "", types.ErrMemberAlreadyExists } - provider.entries[id] = &MemberEntry{ + provider.entries[id] = &memberEntry{ // TODO: Use hash function. - Password: password, - Profile: profile, + password: password, + profile: profile, } return id, nil @@ -99,7 +71,7 @@ func (provider *MemberProviderCtx) UpdateProfile(id string, profile types.Member return types.ErrMemberDoesNotExist } - entry.Profile = profile + entry.profile = profile return nil } @@ -111,7 +83,7 @@ func (provider *MemberProviderCtx) UpdatePassword(id string, password string) er } // TODO: Use hash function. - entry.Password = password + entry.password = password return nil } @@ -122,7 +94,7 @@ func (provider *MemberProviderCtx) Select(id string) (types.MemberProfile, error return types.MemberProfile{}, types.ErrMemberDoesNotExist } - return entry.Profile, nil + return entry.profile, nil } func (provider *MemberProviderCtx) SelectAll(limit int, offset int) (map[string]types.MemberProfile, error) { @@ -131,7 +103,7 @@ func (provider *MemberProviderCtx) SelectAll(limit int, offset int) (map[string] i := 0 for id, entry := range provider.entries { if i >= offset && (limit == 0 || i < offset+limit) { - profiles[id] = entry.Profile + profiles[id] = entry.profile } i = i + 1 diff --git a/internal/member/object/types.go b/internal/member/object/types.go index ca1e519a..a21c2209 100644 --- a/internal/member/object/types.go +++ b/internal/member/object/types.go @@ -4,12 +4,21 @@ import ( "github.com/demodesk/neko/pkg/types" ) -type MemberEntry struct { - Password string `json:"password"` - Profile types.MemberProfile `json:"profile"` +type memberEntry struct { + password string + profile types.MemberProfile +} + +func (m *memberEntry) CheckPassword(password string) bool { + return m.password == password +} + +type User struct { + Username string + Password string + Profile types.MemberProfile } type Config struct { - AdminPassword string - UserPassword string + Users []User } diff --git a/pkg/types/member.go b/pkg/types/member.go index e59bd4a5..4463f4dc 100644 --- a/pkg/types/member.go +++ b/pkg/types/member.go @@ -12,15 +12,15 @@ type MemberProfile struct { Name string `json:"name"` // permissions - IsAdmin bool `json:"is_admin"` - CanLogin bool `json:"can_login"` - CanConnect bool `json:"can_connect"` - CanWatch bool `json:"can_watch"` - CanHost bool `json:"can_host"` - CanShareMedia bool `json:"can_share_media"` - CanAccessClipboard bool `json:"can_access_clipboard"` - SendsInactiveCursor bool `json:"sends_inactive_cursor"` - CanSeeInactiveCursors bool `json:"can_see_inactive_cursors"` + IsAdmin bool `json:"is_admin" mapstructure:"is_admin"` + CanLogin bool `json:"can_login" mapstructure:"can_login"` + CanConnect bool `json:"can_connect" mapstructure:"can_connect"` + CanWatch bool `json:"can_watch" mapstructure:"can_watch"` + CanHost bool `json:"can_host" mapstructure:"can_host"` + CanShareMedia bool `json:"can_share_media" mapstructure:"can_share_media"` + CanAccessClipboard bool `json:"can_access_clipboard" mapstructure:"can_access_clipboard"` + SendsInactiveCursor bool `json:"sends_inactive_cursor" mapstructure:"sends_inactive_cursor"` + CanSeeInactiveCursors bool `json:"can_see_inactive_cursors" mapstructure:"can_see_inactive_cursors"` // plugin scope Plugins map[string]any `json:"plugins"`