From c172590b92b10501cbb5a1243fde5d100917c3d1 Mon Sep 17 00:00:00 2001 From: pigpig Date: Mon, 22 Feb 2021 00:14:40 +0100 Subject: [PATCH] Add registration Can be enabled via the registration config flag. (disabled per default) Fixes gotify/server#395 Co-authored-by: pigpig Co-authored-by: Karmanyaah Malhotra <32671690+karmanyaahm@users.noreply.github.com> Co-authored-by: Jannis Mattheis --- api/application_test.go | 2 +- api/user.go | 34 ++++++++++- api/user_test.go | 111 +++++++++++++++++++++++++++++++++++- auth/authentication.go | 26 +++++++++ auth/authentication_test.go | 38 ++++++++++-- auth/util.go | 15 ++++- auth/util_test.go | 8 +++ config.example.yml | 1 + config/config.go | 1 + docs/spec.json | 1 + router/router.go | 6 +- test/testdb/database.go | 7 +++ ui/src/CurrentUser.ts | 21 +++++++ ui/src/layout/Layout.tsx | 8 ++- ui/src/user/Login.tsx | 33 ++++++++++- ui/src/user/Register.tsx | 99 ++++++++++++++++++++++++++++++++ 16 files changed, 391 insertions(+), 20 deletions(-) create mode 100644 ui/src/user/Register.tsx diff --git a/api/application_test.go b/api/application_test.go index b372377..61236b1 100644 --- a/api/application_test.go +++ b/api/application_test.go @@ -535,6 +535,6 @@ func fakeImage(t *testing.T, path string) { data, err := ioutil.ReadFile("../test/assets/image.png") assert.Nil(t, err) // Write data to dst - err = ioutil.WriteFile(path, data, 0644) + err = ioutil.WriteFile(path, data, 0o644) assert.Nil(t, err) } diff --git a/api/user.go b/api/user.go index c221be6..749c276 100644 --- a/api/user.go +++ b/api/user.go @@ -2,6 +2,8 @@ package api import ( "errors" + "fmt" + "net/http" "github.com/gin-gonic/gin" "github.com/gotify/server/v2/auth" @@ -59,6 +61,7 @@ type UserAPI struct { DB UserDatabase PasswordStrength int UserChangeNotifier *UserChangeNotifier + Registration bool } // GetUsers returns all the users @@ -126,11 +129,14 @@ func (a *UserAPI) GetCurrentUser(ctx *gin.Context) { ctx.JSON(200, toExternalUser(user)) } -// CreateUser creates a user +// CreateUser create a user. // swagger:operation POST /user user createUser // // Create a user. // +// With enabled registration: non admin users can be created without authentication. +// With disabled registrations: users can only be created by admin users. +// // --- // consumes: [application/json] // produces: [application/json] @@ -167,6 +173,32 @@ func (a *UserAPI) CreateUser(ctx *gin.Context) { if success := successOrAbort(ctx, 500, err); !success { return } + + var requestedBy *model.User + uid := auth.TryGetUserID(ctx) + if uid != nil { + requestedBy, err = a.DB.GetUserByID(*uid) + if err != nil { + ctx.AbortWithError(http.StatusInternalServerError, fmt.Errorf("could not get user: %s", err)) + return + } + } + + if requestedBy == nil || !requestedBy.Admin { + status := http.StatusUnauthorized + if requestedBy != nil { + status = http.StatusForbidden + } + if !a.Registration { + ctx.AbortWithError(status, errors.New("you are not allowed to access this api")) + return + } + if internal.Admin { + ctx.AbortWithError(status, errors.New("you are not allowed to create an admin user")) + return + } + } + if existingUser == nil { if success := successOrAbort(ctx, 500, a.DB.CreateUser(internal)); !success { return diff --git a/api/user_test.go b/api/user_test.go index cff9358..119ca6d 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gin-gonic/gin" + "github.com/gotify/server/v2/auth" "github.com/gotify/server/v2/auth/password" "github.com/gotify/server/v2/mode" "github.com/gotify/server/v2/model" @@ -35,7 +36,9 @@ func (s *UserSuite) BeforeTest(suiteName, testName string) { mode.Set(mode.TestDev) s.recorder = httptest.NewRecorder() s.ctx, _ = gin.CreateTestContext(s.recorder) + s.db = testdb.NewDB(s.T()) + s.notifier = new(UserChangeNotifier) s.notifier.OnUserDeleted(func(uint) error { s.notifiedDelete = true @@ -164,15 +167,17 @@ func (s *UserSuite) Test_DeleteUserByID_NotifyFail() { } func (s *UserSuite) Test_CreateUser() { + s.loginAdmin() + assert.False(s.T(), s.notifiedAdd) s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "mylittlepony", "admin": true}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") s.a.CreateUser(s.ctx) - user := &model.UserExternal{ID: 1, Name: "tom", Admin: true} - test.BodyEquals(s.T(), user, s.recorder) assert.Equal(s.T(), 200, s.recorder.Code) + user := &model.UserExternal{ID: 2, Name: "tom", Admin: true} + test.BodyEquals(s.T(), user, s.recorder) if created, err := s.db.GetUserByName("tom"); assert.NoError(s.T(), err) { assert.NotNil(s.T(), created) @@ -181,7 +186,88 @@ func (s *UserSuite) Test_CreateUser() { assert.True(s.T(), s.notifiedAdd) } +func (s *UserSuite) Test_CreateUser_ByNonAdmin() { + s.loginUser() + + s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "1", "admin": false}`)) + s.ctx.Request.Header.Set("Content-Type", "application/json") + + s.a.CreateUser(s.ctx) + + assert.Equal(s.T(), 403, s.recorder.Code) +} + +func (s *UserSuite) Test_CreateUser_Register_ByNonAdmin() { + s.loginUser() + s.a.Registration = true + + s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "1", "admin": false}`)) + s.ctx.Request.Header.Set("Content-Type", "application/json") + + s.a.CreateUser(s.ctx) + + assert.Equal(s.T(), 200, s.recorder.Code) + if created, err := s.db.GetUserByName("tom"); assert.NoError(s.T(), err) { + assert.NotNil(s.T(), created) + } +} + +func (s *UserSuite) Test_CreateUser_Register_Admin_ByNonAdmin() { + s.a.Registration = true + s.loginUser() + + s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "1", "admin": true}`)) + s.ctx.Request.Header.Set("Content-Type", "application/json") + + s.a.CreateUser(s.ctx) + + assert.Equal(s.T(), 403, s.recorder.Code) + s.db.AssertUsernameNotExist("tom") +} + +func (s *UserSuite) Test_CreateUser_Anonymous() { + s.noLogin() + + s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "1", "admin": false}`)) + s.ctx.Request.Header.Set("Content-Type", "application/json") + + s.a.CreateUser(s.ctx) + + assert.Equal(s.T(), 401, s.recorder.Code) + s.db.AssertUsernameNotExist("tom") +} + +func (s *UserSuite) Test_CreateUser_Register_Anonymous() { + s.a.Registration = true + s.noLogin() + + s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "1", "admin": false}`)) + s.ctx.Request.Header.Set("Content-Type", "application/json") + + s.a.CreateUser(s.ctx) + + assert.Equal(s.T(), 200, s.recorder.Code) + if created, err := s.db.GetUserByName("tom"); assert.NoError(s.T(), err) { + assert.NotNil(s.T(), created) + } +} + +func (s *UserSuite) Test_CreateUser_Register_Admin_Anonymous() { + s.a.Registration = true + s.noLogin() + + s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "1", "admin": true}`)) + s.ctx.Request.Header.Set("Content-Type", "application/json") + + s.a.CreateUser(s.ctx) + + assert.Equal(s.T(), 401, s.recorder.Code) + s.db.AssertUsernameNotExist("tom") +} + func (s *UserSuite) Test_CreateUser_NotifyFail() { + s.loginAdmin() + s.notifier.OnUserAdded(func(id uint) error { user, err := s.db.GetUserByID(id) if err != nil { @@ -201,6 +287,8 @@ func (s *UserSuite) Test_CreateUser_NotifyFail() { } func (s *UserSuite) Test_CreateUser_NoPassword() { + s.loginAdmin() + s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "", "admin": true}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") @@ -210,6 +298,8 @@ func (s *UserSuite) Test_CreateUser_NoPassword() { } func (s *UserSuite) Test_CreateUser_NoName() { + s.loginAdmin() + s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "", "pass": "asd", "admin": true}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") @@ -219,7 +309,8 @@ func (s *UserSuite) Test_CreateUser_NoName() { } func (s *UserSuite) Test_CreateUser_NameAlreadyExists() { - s.db.NewUserWithName(1, "tom") + s.loginAdmin() + s.db.NewUserWithName(2, "tom") s.ctx.Request = httptest.NewRequest("POST", "/user", strings.NewReader(`{"name": "tom", "pass": "mylittlepony", "admin": true}`)) s.ctx.Request.Header.Set("Content-Type", "application/json") @@ -333,6 +424,20 @@ func (s *UserSuite) Test_UpdatePassword_EmptyPassword() { assert.True(s.T(), password.ComparePassword(user.Pass, []byte("old"))) } +func (s *UserSuite) loginAdmin() { + s.db.CreateUser(&model.User{ID: 1, Name: "admin", Admin: true}) + auth.RegisterAuthentication(s.ctx, nil, 1, "") +} + +func (s *UserSuite) loginUser() { + s.db.CreateUser(&model.User{ID: 1, Name: "user", Admin: false}) + auth.RegisterAuthentication(s.ctx, nil, 1, "") +} + +func (s *UserSuite) noLogin() { + auth.RegisterAuthentication(s.ctx, nil, 0, "") +} + func externalOf(user *model.User) *model.UserExternal { return &model.UserExternal{Name: user.Name, Admin: user.Admin, ID: user.ID} } diff --git a/auth/authentication.go b/auth/authentication.go index 97d8567..fbe183d 100644 --- a/auth/authentication.go +++ b/auth/authentication.go @@ -133,3 +133,29 @@ func (a *Auth) requireToken(auth authenticate) gin.HandlerFunc { ctx.AbortWithError(401, errors.New("you need to provide a valid access token or user credentials to access this api")) } } + +func (a *Auth) Optional() gin.HandlerFunc { + return func(ctx *gin.Context) { + token := a.tokenFromQueryOrHeader(ctx) + user, err := a.userFromBasicAuth(ctx) + if err != nil { + RegisterAuthentication(ctx, nil, 0, "") + ctx.Next() + return + } + + if user != nil { + RegisterAuthentication(ctx, user, user.ID, token) + ctx.Next() + return + } else if token != "" { + if tokenClient, err := a.DB.GetClientByToken(token); err == nil && tokenClient != nil { + RegisterAuthentication(ctx, user, tokenClient.UserID, token) + ctx.Next() + return + } + } + RegisterAuthentication(ctx, nil, 0, "") + ctx.Next() + } +} diff --git a/auth/authentication_test.go b/auth/authentication_test.go index f76da34..b83900f 100644 --- a/auth/authentication_test.go +++ b/auth/authentication_test.go @@ -80,12 +80,13 @@ func (s *AuthenticationSuite) TestQueryToken() { s.assertQueryRequest("token", "clienttoken_admin", s.auth.RequireAdmin, 200) } -func (s *AuthenticationSuite) assertQueryRequest(key, value string, f fMiddleware, code int) { +func (s *AuthenticationSuite) assertQueryRequest(key, value string, f fMiddleware, code int) (ctx *gin.Context) { recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ = gin.CreateTestContext(recorder) ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/?%s=%s", key, value), nil) f()(ctx) assert.Equal(s.T(), code, recorder.Code) + return } func (s *AuthenticationSuite) TestNothingProvided() { @@ -160,13 +161,42 @@ func (s *AuthenticationSuite) TestBasicAuth() { s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.RequireAdmin, 401) } -func (s *AuthenticationSuite) assertHeaderRequest(key, value string, f fMiddleware, code int) { +func (s *AuthenticationSuite) TestOptionalAuth() { + // various invalid users + ctx := s.assertQueryRequest("token", "ergerogerg", s.auth.Optional, 200) + assert.Nil(s.T(), TryGetUserID(ctx)) + ctx = s.assertHeaderRequest("X-Gotify-Key", "ergerogerg", s.auth.Optional, 200) + assert.Nil(s.T(), TryGetUserID(ctx)) + ctx = s.assertHeaderRequest("Authorization", "Basic bm90ZXhpc3Rpbmc6cHc=", s.auth.Optional, 200) + assert.Nil(s.T(), TryGetUserID(ctx)) + ctx = s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHd4", s.auth.Optional, 200) + assert.Nil(s.T(), TryGetUserID(ctx)) + ctx = s.assertQueryRequest("tokenx", "clienttoken", s.auth.Optional, 200) + assert.Nil(s.T(), TryGetUserID(ctx)) + ctx = s.assertQueryRequest("token", "apptoken_admin", s.auth.Optional, 200) + assert.Nil(s.T(), TryGetUserID(ctx)) + + // user existing:pw + ctx = s.assertHeaderRequest("Authorization", "Basic ZXhpc3Rpbmc6cHc=", s.auth.Optional, 200) + assert.Equal(s.T(), uint(1), *TryGetUserID(ctx)) + ctx = s.assertQueryRequest("token", "clienttoken", s.auth.Optional, 200) + assert.Equal(s.T(), uint(1), *TryGetUserID(ctx)) + + // user admin:pw + ctx = s.assertHeaderRequest("Authorization", "Basic YWRtaW46cHc=", s.auth.Optional, 200) + assert.Equal(s.T(), uint(2), *TryGetUserID(ctx)) + ctx = s.assertQueryRequest("token", "clienttoken_admin", s.auth.Optional, 200) + assert.Equal(s.T(), uint(2), *TryGetUserID(ctx)) +} + +func (s *AuthenticationSuite) assertHeaderRequest(key, value string, f fMiddleware, code int) (ctx *gin.Context) { recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) + ctx, _ = gin.CreateTestContext(recorder) ctx.Request = httptest.NewRequest("GET", "/", nil) ctx.Request.Header.Set(key, value) f()(ctx) assert.Equal(s.T(), code, recorder.Code) + return } type fMiddleware func() gin.HandlerFunc diff --git a/auth/util.go b/auth/util.go index 19d8097..156648e 100644 --- a/auth/util.go +++ b/auth/util.go @@ -14,16 +14,25 @@ func RegisterAuthentication(ctx *gin.Context, user *model.User, userID uint, tok // GetUserID returns the user id which was previously registered by RegisterAuthentication. func GetUserID(ctx *gin.Context) uint { + id := TryGetUserID(ctx) + if id == nil { + panic("token and user may not be null") + } + return *id +} + +// TryGetUserID returns the user id or nil if one is not set. +func TryGetUserID(ctx *gin.Context) *uint { user := ctx.MustGet("user").(*model.User) if user == nil { userID := ctx.MustGet("userid").(uint) if userID == 0 { - panic("token and user may not be null") + return nil } - return userID + return &userID } - return user.ID + return &user.ID } // GetTokenID returns the tokenID. diff --git a/auth/util_test.go b/auth/util_test.go index ee6af5a..b42aef1 100644 --- a/auth/util_test.go +++ b/auth/util_test.go @@ -29,6 +29,7 @@ func (s *UtilSuite) Test_getID() { assert.Panics(s.T(), func() { s.expectUserIDWith(nil, 0, 0) }) + s.expectTryUserIDWith(nil, 0, nil) } func (s *UtilSuite) Test_getToken() { @@ -44,3 +45,10 @@ func (s *UtilSuite) expectUserIDWith(user *model.User, tokenUserID, expectedID u actualID := GetUserID(ctx) assert.Equal(s.T(), expectedID, actualID) } + +func (s *UtilSuite) expectTryUserIDWith(user *model.User, tokenUserID uint, expectedID *uint) { + ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) + RegisterAuthentication(ctx, user, tokenUserID, "") + actualID := TryGetUserID(ctx) + assert.Equal(s.T(), expectedID, actualID) +} diff --git a/config.example.yml b/config.example.yml index a7ef8fb..15e7e7c 100644 --- a/config.example.yml +++ b/config.example.yml @@ -50,3 +50,4 @@ defaultuser: # on database creation, gotify creates an admin user passstrength: 10 # the bcrypt password strength (higher = better but also slower) uploadedimagesdir: data/images # the directory for storing uploaded images pluginsdir: data/plugins # the directory where plugin resides +registration: false # enable registrations diff --git a/config/config.go b/config/config.go index 0d7de89..a1410c1 100644 --- a/config/config.go +++ b/config/config.go @@ -51,6 +51,7 @@ type Configuration struct { PassStrength int `default:"10"` UploadedImagesDir string `default:"data/images"` PluginsDir string `default:"data/plugins"` + Registration bool `default:"false"` } func configFiles() []string { diff --git a/docs/spec.json b/docs/spec.json index e82ca91..c499ddd 100644 --- a/docs/spec.json +++ b/docs/spec.json @@ -1615,6 +1615,7 @@ "basicAuth": [] } ], + "description": "With enabled registration: non admin users can be created without authentication.\nWith disabled registrations: users can only be created by admin users.", "consumes": [ "application/json" ], diff --git a/router/router.go b/router/router.go index 9779180..edb9736 100644 --- a/router/router.go +++ b/router/router.go @@ -39,7 +39,7 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co ImageDir: conf.UploadedImagesDir, } userChangeNotifier := new(api.UserChangeNotifier) - userHandler := api.UserAPI{DB: db, PasswordStrength: conf.PassStrength, UserChangeNotifier: userChangeNotifier} + userHandler := api.UserAPI{DB: db, PasswordStrength: conf.PassStrength, UserChangeNotifier: userChangeNotifier, Registration: conf.Registration} pluginManager, err := plugin.NewManager(db, conf.PluginsDir, g.Group("/plugin/:id/custom/"), streamHandler) if err != nil { @@ -82,6 +82,8 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co } } + g.Group("/user").Use(authentication.Optional()).POST("", userHandler.CreateUser) + g.OPTIONS("/*any") // swagger:operation GET /version version getVersion @@ -157,8 +159,6 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co authAdmin.GET("", userHandler.GetUsers) - authAdmin.POST("", userHandler.CreateUser) - authAdmin.DELETE("/:id", userHandler.DeleteUserByID) authAdmin.GET("/:id", userHandler.GetUserByID) diff --git a/test/testdb/database.go b/test/testdb/database.go index d90e764..2abe254 100644 --- a/test/testdb/database.go +++ b/test/testdb/database.go @@ -183,6 +183,13 @@ func (d *Database) AssertUserNotExist(id uint) { } } +// AssertUsernameNotExist asserts that the user does not exist. +func (d *Database) AssertUsernameNotExist(name string) { + if user, err := d.GetUserByName(name); assert.NoError(d.t, err) { + assert.True(d.t, user == nil, "user %d must not exist", name) + } +} + // AssertClientNotExist asserts that the client does not exist. func (d *Database) AssertClientNotExist(id uint) { if client, err := d.GetClientByID(id); assert.NoError(d.t, err) { diff --git a/ui/src/CurrentUser.ts b/ui/src/CurrentUser.ts index 3016cc6..6de4edb 100644 --- a/ui/src/CurrentUser.ts +++ b/ui/src/CurrentUser.ts @@ -42,6 +42,27 @@ export class CurrentUser { window.localStorage.setItem(tokenKey, token); }; + public register = async (name: string, pass: string): Promise => + axios + .create() + .post(config.get('url') + 'user', {name, pass}) + .then(() => { + this.snack('User Created. Logging in...'); + this.login(name, pass); + return true; + }) + .catch((error: AxiosError) => { + if (!error || !error.response) { + this.snack('No network connection or server unavailable.'); + return false; + } + const {data} = error.response; + this.snack( + `Register failed: ${data?.error ?? 'unknown'}: ${data?.errorDescription ?? ''}` + ); + return false; + }); + public login = async (username: string, password: string) => { this.loggedIn = false; this.authenticating = true; diff --git a/ui/src/layout/Layout.tsx b/ui/src/layout/Layout.tsx index b6c6049..286fd6a 100644 --- a/ui/src/layout/Layout.tsx +++ b/ui/src/layout/Layout.tsx @@ -67,12 +67,15 @@ class Layout extends React.Component< private version = Layout.defaultVersion; @observable private navOpen = false; + @observable + private showRegister = true; //TODO https://github.com/gotify/server/pull/394#discussion_r650559205 private setNavOpen(open: boolean) { this.navOpen = open; } public componentDidMount() { + this.registration = true; //TODO https://github.com/gotify/server/pull/394#discussion_r650559205 if (this.version === Layout.defaultVersion) { axios.get(config.get('url') + 'version').then((resp: AxiosResponse) => { this.version = resp.data.version; @@ -88,7 +91,7 @@ class Layout extends React.Component< } public render() { - const {version, showSettings, currentTheme} = this; + const {version, showSettings, currentTheme, showRegister} = this; const { classes, currentUser: { @@ -101,7 +104,8 @@ class Layout extends React.Component< }, } = this.props; const theme = themeMap[currentTheme]; - const loginRoute = () => (loggedIn ? : ); + const loginRoute = () => + loggedIn ? : ; return ( diff --git a/ui/src/user/Login.tsx b/ui/src/user/Login.tsx index f55be9a..6f7ac61 100644 --- a/ui/src/user/Login.tsx +++ b/ui/src/user/Login.tsx @@ -7,18 +7,25 @@ import DefaultPage from '../common/DefaultPage'; import {observable} from 'mobx'; import {observer} from 'mobx-react'; import {inject, Stores} from '../inject'; +import RegistrationDialog from './Register'; + +type Props = Stores<'currentUser'> & { + showRegister: boolean; +}; @observer -class Login extends Component> { +class Login extends Component { @observable private username = ''; @observable private password = ''; + @observable + private registerDialog = false; public render() { - const {username, password} = this; + const {username, password, registerDialog} = this; return ( - +
@@ -52,6 +59,12 @@ class Login extends Component> {
+ {registerDialog && ( + (this.registerDialog = false)} + fOnSubmit={this.props.currentUser.register} + /> + )}
); } @@ -61,6 +74,20 @@ class Login extends Component> { this.props.currentUser.login(this.username, this.password); }; + private registerButton = () => { + if (this.props.showRegister) + return ( + + ); + else return null; + }; + private preventDefault = (e: FormEvent) => e.preventDefault(); } diff --git a/ui/src/user/Register.tsx b/ui/src/user/Register.tsx new file mode 100644 index 0000000..9ef3470 --- /dev/null +++ b/ui/src/user/Register.tsx @@ -0,0 +1,99 @@ +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import TextField from '@material-ui/core/TextField'; +import Tooltip from '@material-ui/core/Tooltip'; +import React, {ChangeEvent, Component} from 'react'; + +interface IProps { + name?: string; + fClose: VoidFunction; + fOnSubmit: (name: string, pass: string) => Promise; +} + +interface IState { + name: string; + pass: string; +} + +export default class RegistrationDialog extends Component { + public state = { + name: '', + pass: '', + }; + + public render() { + const {fClose, fOnSubmit} = this.props; + const {name, pass} = this.state; + const namePresent = this.state.name.length !== 0; + const passPresent = this.state.pass.length !== 0; + const submitAndClose = (): void => { + fOnSubmit(name, pass).then((success) => { + if (success) { + fClose(); + } + }); + }; + return ( + + Registration + + + + + + + +
+ +
+
+
+
+ ); + } + + private handleChange(propertyName: string, event: ChangeEvent) { + const state = this.state; + state[propertyName] = event.target.value; + this.setState(state); + } +}