diff --git a/evennia/commands/default/unloggedin.py b/evennia/commands/default/unloggedin.py index 9affc8fa46..65734611df 100644 --- a/evennia/commands/default/unloggedin.py +++ b/evennia/commands/default/unloggedin.py @@ -12,7 +12,7 @@ from evennia.objects.models import ObjectDB from evennia.server.models import ServerConfig from evennia.comms.models import ChannelDB -from evennia.utils import create, logger, utils, ansi +from evennia.utils import create, logger, utils from evennia.commands.cmdhandler import CMD_LOGINSTART COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) @@ -516,12 +516,12 @@ class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS): self.session.sessionhandler.session_portal_sync(self.session) -def _create_player(session, playername, password, permissions, typeclass=None): +def _create_player(session, playername, password, permissions, typeclass=None, email=None): """ Helper function, creates a player of the specified typeclass. """ try: - new_player = create.create_player(playername, None, password, permissions=permissions, typeclass=typeclass) + new_player = create.create_player(playername, email, password, permissions=permissions, typeclass=typeclass) except Exception as e: session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e) @@ -535,7 +535,7 @@ def _create_player(session, playername, password, permissions, typeclass=None): # join the new player to the public channel pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"]) - if not pchannel.connect(new_player): + if not pchannel or not pchannel.connect(new_player): string = "New player '%s' could not connect to public channel!" % new_player.key logger.log_err(string) return new_player diff --git a/evennia/contrib/email_login.py b/evennia/contrib/email_login.py index 468b6837b6..0684e91514 100644 --- a/evennia/contrib/email_login.py +++ b/evennia/contrib/email_login.py @@ -118,7 +118,7 @@ class CmdUnconnectedConnect(MuxCommand): # actually do the login. This will call all hooks. session.sessionhandler.login(session, player) - +from evennia.commands.default import unloggedin as default_unloggedin class CmdUnconnectedCreate(MuxCommand): """ Create a new account. @@ -161,108 +161,86 @@ class CmdUnconnectedCreate(MuxCommand): """Do checks and create account""" session = self.caller - try: playername, email, password = self.playerinfo except ValueError: string = "\n\r Usage (without <>): create \"\" " session.msg(string) return - if not re.findall('^[\w. @+-]+$', playername) or not (0 < len(playername) <= 30): - session.msg("\n\r Playername can be max 30 characters, or less. Letters, spaces," - " digits and @/./+/-/_ only.") # this echoes the restrictions made by django's auth module. - return if not email or not password: session.msg("\n\r You have to supply an e-mail address followed by a password.") return - if not utils.validate_email_address(email): # check so the email at least looks ok. session.msg("'%s' is not a valid e-mail address." % email) return - - # Run sanity and security checks - - if PlayerDB.objects.filter(username=playername): - # player already exists + # sanity checks + if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30): + # this echoes the restrictions made by django's auth + # module (except not allowing spaces, for convenience of + # logging in). + string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only." + session.msg(string) + return + # strip excessive spaces in playername + playername = re.sub(r"\s+", " ", playername).strip() + if PlayerDB.objects.filter(username__iexact=playername): + # player already exists (we also ignore capitalization here) session.msg("Sorry, there is already a player with the name '%s'." % playername) return if PlayerDB.objects.get_player_from_email(email): # email already set on a player session.msg("Sorry, there is already a player with that email address.") return - if len(password) < 3: - # too short password - string = "Your password must be at least 3 characters or longer." - string += "\n\rFor best security, make it at least 8 characters long, " - string += "avoid making it a real word and mix numbers into it." + # Reserve playernames found in GUEST_LIST + if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST): + string = "\n\r That name is reserved. Please choose another Playername." session.msg(string) return + if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)): + string = "\n\r Password should be longer than 3 characers. Letters, spaces, digits and @/./+/-/_/' only." \ + "\nFor best security, make it longer than 8 characters. You can also use a phrase of" \ + "\nmany words if you enclose the password in double quotes." + session.msg(string) + return + + # Check IP and/or name bans + bans = ServerConfig.objects.conf("server_bans") + if bans and (any(tup[0] == playername.lower() for tup in bans) + or + any(tup[2].match(session.address) for tup in bans if tup[2])): + # this is a banned IP or name! + string = "|rYou have been banned and cannot continue from here." \ + "\nIf you feel this ban is in error, please email an admin.|x" + session.msg(string) + session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") + return # everything's ok. Create the new player account. try: - default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) - - typeclass = settings.BASE_CHARACTER_TYPECLASS permissions = settings.PERMISSION_PLAYER_DEFAULT - - try: - new_player = create.create_player(playername, email, password, permissions=permissions) - - except Exception as e: - session.msg("There was an error creating the default Player/Character:\n%s\n" - " If this problem persists, contact an admin." % e) - logger.log_trace() - return - - # This needs to be set so the engine knows this player is - # logging in for the first time. (so it knows to call the right - # hooks during login later) - new_player.db.FIRST_LOGIN = True - - # join the new player to the public channel - pchanneldef = settings.CHANNEL_PUBLIC - if pchanneldef: - pchannel = ChannelDB.objects.get_channel(pchanneldef[0]) - if not pchannel.connect(new_player): - string = "New player '%s' could not connect to public channel!" % new_player.key - logger.log_err(string) - - if MULTISESSION_MODE < 2: - # if we only allow one character, create one with the same name as Player - # (in mode 2, the character must be created manually once logging in) - new_character = create.create_object(typeclass, key=playername, - location=default_home, home=default_home, - permissions=permissions) - # set playable character list - new_player.db._playable_characters.append(new_character) - - # allow only the character itself and the player to puppet this character (and Immortals). - new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % - (new_character.id, new_player.id)) - - # If no description is set, set a default description - if not new_character.db.desc: - new_character.db.desc = "This is a Player." - # We need to set this to have @ic auto-connect to this character - new_player.db._last_puppet = new_character - - # tell the caller everything went well. - string = "A new account '%s' was created. Welcome!" - if " " in playername: - string += "\n\nYou can now log in with the command 'connect %s '." - else: - string += "\n\nYou can now log with the command 'connect %s '." - session.msg(string % (playername, email)) + typeclass = settings.BASE_CHARACTER_TYPECLASS + new_player = default_unloggedin._create_player(session, playername, password, permissions, email=email) + if new_player: + if MULTISESSION_MODE < 2: + default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) + default_unloggedin._create_character(session, new_player, typeclass, default_home, permissions) + # tell the caller everything went well. + string = "A new account '%s' was created. Welcome!" + if " " in playername: + string += "\n\nYou can now log in with the command 'connect \"%s\" '." + else: + string += "\n\nYou can now log with the command 'connect %s '." + session.msg(string % (playername, email)) except Exception: # We are in the middle between logged in and -not, so we have # to handle tracebacks ourselves at this point. If we don't, # we won't see any errors at all. - session.msg("An error occurred. Please e-mail an admin if the problem persists.") + raise + session.msg("%sAn error occurred. Please e-mail an admin if the problem persists.") logger.log_trace() - class CmdUnconnectedQuit(MuxCommand): """ We maintain a different version of the `quit` command @@ -276,8 +254,7 @@ class CmdUnconnectedQuit(MuxCommand): def func(self): """Simply close the connection.""" session = self.caller - session.msg("Good bye! Disconnecting ...") - session.session_disconnect() + session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") class CmdUnconnectedLook(MuxCommand): diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 7d11d97fa0..a8850fc791 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -489,6 +489,18 @@ class TestDice(CommandTest): self.call(dice.CmdDice(), "100000d1000", "The maximum roll allowed is 10000d10000.") self.call(dice.CmdDice(), "/secret 3d6 + 4", "You roll 3d6 + 4 (secret, not echoed).") +# Test email-login +from evennia.contrib import email_login - +class TestEmailLogin(CommandTest): + def test_connect(self): + self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test", "The email 'mytest@test.com' does not match any accounts.") + self.call(email_login.CmdUnconnectedCreate(), '"mytest" mytest@test.com test11111', "A new account 'mytest' was created. Welcome!") + self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test11111", "", caller=self.player.sessions.get()[0]) + def test_quit(self): + self.call(email_login.CmdUnconnectedQuit(), "", "", caller=self.player.sessions.get()[0]) + def test_unconnectedlook(self): + self.call(email_login.CmdUnconnectedLook(), "", "==========") + def test_unconnectedhelp(self): + self.call(email_login.CmdUnconnectedHelp(), "", "You are not yet logged into the game.")