From e16d0ff6592f234ce11c66c8cf65a826351e2089 Mon Sep 17 00:00:00 2001 From: Jennifer Taylor Date: Sun, 30 Aug 2026 16:43:09 +0000 Subject: [PATCH] Add server setting to allow unlinked card registration. --- bemani/data/config.py | 4 + bemani/frontend/account/account.py | 62 ++++++++++++---- bemani/frontend/app.py | 8 +- .../frontend/templates/account/register.html | 28 ++++--- config/server.yaml | 74 +++++++++++++------ 5 files changed, 123 insertions(+), 53 deletions(-) diff --git a/bemani/data/config.py b/bemani/data/config.py index f2af3e0..5cf0c5a 100644 --- a/bemani/data/config.py +++ b/bemani/data/config.py @@ -83,6 +83,10 @@ class Server: def allow_raw_ids(self) -> bool: return bool(self.__config.get("server", {}).get("allow_raw_ids", False)) + @property + def allow_unlinked_signups(self) -> bool: + return bool(self.__config.get("server", {}).get("allow_unlinked_signups", False)) + @property def region(self) -> int: region = int(self.__config.get("server", {}).get("region", RegionConstants.USA)) diff --git a/bemani/frontend/account/account.py b/bemani/frontend/account/account.py index 4ac1c2c..a7667a3 100644 --- a/bemani/frontend/account/account.py +++ b/bemani/frontend/account/account.py @@ -210,22 +210,48 @@ def register() -> Response: error("Invalid card number!") return register_display(card_number, username, email) - # Now, see if this card ID exists already - userid = g.data.local.user.from_cardid(cardid) - if userid is None: - error("This card has not been used on the network yet!") - return register_display(card_number, username, email) + if g.config.server.allow_unlinked_signups: + # We only need to check if the card is in use already + # by another user, or if the PIN was invalid on a card + # the user is trying to claim. We don't need to verify + # that the card has been seen yet. + userid = g.data.local.user.from_cardid(cardid) + if userid is not None: + # Now, make sure this user doesn't already have an account + user = g.data.local.user.get_user(userid) + if user.username is not None or user.email is not None: + error("This card is already in use!") + return register_display(card_number, username, email) - # Now, make sure this user doesn't already have an account - user = g.data.local.user.get_user(userid) - if user.username is not None or user.email is not None: - error("This card is already in use!") - return register_display(card_number, username, email) + # Now, see if the pin is correct + if not g.data.local.user.validate_pin(userid, pin): + error("The entered PIN does not match the PIN on the card!") + return register_display(card_number, username, email) - # Now, see if the pin is correct - if not g.data.local.user.validate_pin(userid, pin): - error("The entered PIN does not match the PIN on the card!") - return register_display(card_number, username, email) + else: + # We need to make sure the PIN they proposed is at least + # valid, because we're going to create a card with this PIN. + if not valid_pin(pin, "card"): + error("Invalid PIN, must be exactly 4 digits!") + return register_display(card_number, username, email) + + else: + # Now, see if this card ID exists already + userid = g.data.local.user.from_cardid(cardid) + if userid is None: + error("This card has not been used on the network yet!") + return register_display(card_number, username, email) + + # Now, make sure this user doesn't already have an account + user = g.data.local.user.get_user(userid) + if user.username is not None or user.email is not None: + error("This card is already in use!") + return register_display(card_number, username, email) + + # Now, see if the pin is correct + if not g.data.local.user.validate_pin(userid, pin): + error("The entered PIN does not match the PIN on the card!") + return register_display(card_number, username, email) # Now, see if the username is valid if not valid_username(username): @@ -252,6 +278,14 @@ def register() -> Response: error("Password is not long enough!") return register_display(card_number, username, email) + if g.config.server.allow_unlinked_signups: + if userid is None: + userid = g.data.local.user.create_account(cardid, pin) + user = g.data.local.user.get_user(userid) + + if userid is None or user is None: + raise Exception("Logic error, shouldn't get to this point without a user!") + # Now, create the account. user.username = username user.email = email diff --git a/bemani/frontend/app.py b/bemani/frontend/app.py index b2c90d0..536089d 100644 --- a/bemani/frontend/app.py +++ b/bemani/frontend/app.py @@ -2,7 +2,7 @@ import mimetypes import os import re import traceback -from typing import Callable, Dict, Any, Optional, List +from typing import Callable, Dict, Any, Optional, List, Literal from react.jsx import JSXTransformer # type: ignore from flask import ( Flask, @@ -320,13 +320,13 @@ def valid_username(username: str) -> bool: return re.match(r"^[a-zA-Z0-9_]+$", username) is not None -def valid_pin(pin: str, type: str) -> bool: +def valid_pin(pin: str, type: Literal["card", "arcade"]) -> bool: if type == "card": return re.match(r"^\d\d\d\d$", pin) is not None elif type == "arcade": return re.match(r"^\d\d\d\d\d\d\d\d$", pin) is not None else: - return False + return False # type: ignore # Define useful functions for jnija2 @@ -356,6 +356,8 @@ def navigation() -> Dict[str, Any]: custom_config = {} if g.config.server.allow_raw_ids: custom_config["allow_raw_ids"] = True + if g.config.server.allow_unlinked_signups: + custom_config["allow_unlinked_signups"] = True # Look up the logged in user ID. try: diff --git a/bemani/frontend/templates/account/register.html b/bemani/frontend/templates/account/register.html index c30c1c2..2c858d2 100644 --- a/bemani/frontend/templates/account/register.html +++ b/bemani/frontend/templates/account/register.html @@ -5,19 +5,23 @@

- To register an account on this network you will need to have played at least - one credit on a game linked to this network. If you have not done so you cannot - register an account. Enter the card number and PIN of the card you have used to - play on this network. - {% if custom_config.get("allow_raw_ids", False) %} - The card number is the 16 digit code found on the back of an - e-AMUSEMENT card or displayed in-game when you scan an Amusement - IC card. For convenience, you can also enter the raw 16 digit card - ID usually starting with E004. + {% if custom_config.get("allow_unlinked_signups", False) %} + If you have played one or more credits on a game linked to this network you can + claim that card when creating your account. Enter the card number which is the + 16 digit code found on the back of an e-AMUSEMENT card or displayed in-game when + you scan an Amusement IC card. Make sure you use the same PIN as you did when + you registered your card in-game. If you haven't played yet, enter the card + number and desired PIN for the card you want to use. {% else %} - The card number is the 16 digit code found on the back of an - e-AMUSEMENT card or displayed in-game when you scan an Amusement - IC card. + To register an account on this network you will need to have played at least + one credit on a game linked to this network. If you have not done so you cannot + register an account. Enter the card number and PIN of the card you have used to + play on this network. The card number is the 16 digit code found on the back of + an e-AMUSEMENT card or displayed in-game when you scan an Amusement IC card. + {% endif %} + {% if custom_config.get("allow_raw_ids", False) %} + For convenience, you can also enter the raw 16 digit card ID usually starting + with E004. {% endif %}

diff --git a/config/server.yaml b/config/server.yaml index cac384e..04a4969 100644 --- a/config/server.yaml +++ b/config/server.yaml @@ -8,29 +8,37 @@ database: user: "bemani" # Password of said user. password: "bemani" - # Force the network to read-only mode, refusing to write to the DB - # except for creating/destroying frontend sessions to enable login. - # Set this to False or delete this to run in production mode. + # Force the network to read-only mode, refusing to write to the DB except + # for creating/destroying frontend sessions to enable login. Set this to + # False or delete this to run in production mode. Can be useful when testing + # bug fixes against a production database safely. read_only: False # Core server settings, required so that the backend knows what to tell games for core # routing and server URLs. server: - # Advertised server IP or DNS entry games will connect to. + # Advertised server IP or DNS entry games will connect to. Should be routable + # from a connecting game's perspective and ideally match the DNS entry or IP + # placed in the game's ea3 config. address: "192.168.0.1" - # Advertised keepalive address, must be globally pingable. Delete - # this to use the address above instead of a unique keepalive address. + # Advertised keepalive address, must be globally pingable. Delete this to + # use the address above instead of a unique keepalive address. keepalive: "127.0.0.1" - # What port on the above address games will connect to. + # What port on the above address games will connect to. Should be the public + # port for the server hosting this instance. port: 80 # Whether games should connect over HTTPS. https: False - # Advertised frontend URI. Delete this to mask the frontend address. + # Advertised frontend URI, displayed on the login screen of many games. Delete + # this to mask the frontend address. uri: "https://eagate.573.jp" - # URI that users hitting the GET interface will be redirected to. - # Delete this to return an HTTP error instead of redirecting. + # URI that users hitting the GET interface will be redirected to. Delete this + # to return an HTTP error instead of redirecting. redirect: "https://eagate.573.jp" - # Whether PCBIDs must be added to the network before games will work. + # Whether PCBIDs must be added to the network before games will work. If set + # to True, all PCBIDs must be recognized or the game will be denied access to + # this instance. If set to False, unrecognized PCBIDs will be added to the + # internal list of known PCBIDs as they connect. enforce_pcbid: False # How many PCBIDs an arcade owner can grant to themselves on the arcade # page. Note that this setting is irrelevant if PCBID enforcing is off. @@ -41,6 +49,13 @@ server: # still display Card IDs as they appear in-game and on the back of actual # cards, users can type in the raw ID as a convenience. allow_raw_ids: False + # Whether the register new account page allows for unlinked sign-ups or not. + # With this enabled, somebody can enter any valid card ID to create an + # account associated with that card, even if the card hasn't been used to + # play on the network yet. Only when entering an existing card will the + # system check the PIN to verify that they are the owner of the card. With + # this disabled, accounts must be linked to an existing card and valid PIN. + allow_unlinked_signups: False # Default region for this network (set to USA by default). See RegionConstants # for details on acceptible values. The range of accepted values is 1-56 matching # the 56 normal regions found in RegionConstants, and 1000 for "Europe" and @@ -50,8 +65,8 @@ server: # Delete this setting to force games to display "Unobtained" instead. area: "USA" -# Webhook URLs. These allow for game scores from games with scorecard support to be broadcasted to outside services. -# Delete this to disable this support. +# Webhook URLs. These allow for game scores from games with scorecard support to be +# broadcasted to outside services. Delete this to disable this support. webhooks: discord: iidx: @@ -59,20 +74,22 @@ webhooks: pnm: - "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi" -# Assets URLs. These allow for in-game asset rendering on the front end. Delete this to disable asset rendering. +# Assets URLs. These allow for in-game asset rendering on the front end. Delete this +# to disable asset rendering. assets: jubeat: emblems: "/directory/where/you/output/emblem/assets" -# Global PASESLI settings, which can be overridden on a per-arcade basis. These form the default settings. +# Global PASESLI settings, which can be overridden on a per-arcade basis. These form +# the default settings and are mainly useful when PCBID enforcement is disabled. paseli: # Whether PASELI is enabled on the network. enabled: True # Whether infinite PASELI balance is enabled on the network. infinite: True -# Game series to provide support for. Disabling something here hides it from the frontend and makes the backend -# ignore games coming from that series. +# Game series to provide support for. Disabling something here hides it from the +# frontend and makes the backend ignore games coming from that series. support: # Bishi Bashi frontend/backend enabled. bishi: True @@ -95,11 +112,14 @@ support: # SDVX frontend/backend enabled. sdvx: True -# Key used to encrypt cookies, should be unique per network instance. +# Key used to encrypt cookies, should be unique per network instance. Once chosen +# you should not change this unless you want to invalidate every existing login. secret_key: 'this_is_a_secret_please_change_me' -# Name of this network. +# Name of this network, displayed in various places on the frontend and on the admin +# page when federating with another instance on the Data API page. name: 'e-AMUSEMENT Network' -# Administrative contact for this network. +# Administrative contact for this network, displayed when federating with other instances +# using the Data API. email: 'nobody@nowhere.com' # Cache DIR, should point somewhere other than /tmp for production instances that wish # to use filesystem caching. For memcached, delete this value. @@ -107,10 +127,16 @@ cache_dir: '/tmp' # memcached server, should point somewhere other than this bogus value for production # instances that wish to use memcached backend. For filesystem caching, delete this value. memcached_server: 1.2.3.4:5678 -# Number of seconds to preserve event logs before deleting them. -# Set to zero or delete to disable deleting logs. +# Number of seconds to preserve event logs before deleting them. Set to zero or delete +# this to disable deleting logs. event_log_duration: 2592000 # Whether we log verbosely (full packet request and response) to web server logs or not. -verbose: true -# Frontend theme directory where sitewide CSS and favicon should be found. +# Keeping this on is recommended because you can often replay packets found in the logs +# in specific circumstances after fixing a bug or dealing with a temporary outage. Turn +# this off if you are dealing with excessive log files on a larger instance. +verbose: True +# Frontend theme directory where sitewide CSS and favicon should be found. A "default" +# and a "dark" theme ship standard, as found in the "bemani/frontend/static/themes" +# directory. If you want to create your own theme but don't want to deal with merge +# conflicts you can copy one of these as the base and place it in a new directory. theme: "default"