Add server setting to allow unlinked card registration.

This commit is contained in:
Jennifer Taylor
2026-08-30 16:43:09 +00:00
parent f85e70a431
commit e16d0ff659
5 changed files with 123 additions and 53 deletions
+4
View File
@@ -83,6 +83,10 @@ class Server:
def allow_raw_ids(self) -> bool: def allow_raw_ids(self) -> bool:
return bool(self.__config.get("server", {}).get("allow_raw_ids", False)) 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 @property
def region(self) -> int: def region(self) -> int:
region = int(self.__config.get("server", {}).get("region", RegionConstants.USA)) region = int(self.__config.get("server", {}).get("region", RegionConstants.USA))
+48 -14
View File
@@ -210,22 +210,48 @@ def register() -> Response:
error("Invalid card number!") error("Invalid card number!")
return register_display(card_number, username, email) return register_display(card_number, username, email)
# Now, see if this card ID exists already if g.config.server.allow_unlinked_signups:
userid = g.data.local.user.from_cardid(cardid) # We only need to check if the card is in use already
if userid is None: # by another user, or if the PIN was invalid on a card
error("This card has not been used on the network yet!") # the user is trying to claim. We don't need to verify
return register_display(card_number, username, email) # 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 # Now, see if the pin is correct
user = g.data.local.user.get_user(userid) if not g.data.local.user.validate_pin(userid, pin):
if user.username is not None or user.email is not None: error("The entered PIN does not match the PIN on the card!")
error("This card is already in use!") return register_display(card_number, username, email)
return register_display(card_number, username, email)
# Now, see if the pin is correct else:
if not g.data.local.user.validate_pin(userid, pin): # We need to make sure the PIN they proposed is at least
error("The entered PIN does not match the PIN on the card!") # valid, because we're going to create a card with this PIN.
return register_display(card_number, username, email) 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 # Now, see if the username is valid
if not valid_username(username): if not valid_username(username):
@@ -252,6 +278,14 @@ def register() -> Response:
error("Password is not long enough!") error("Password is not long enough!")
return register_display(card_number, username, email) 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. # Now, create the account.
user.username = username user.username = username
user.email = email user.email = email
+5 -3
View File
@@ -2,7 +2,7 @@ import mimetypes
import os import os
import re import re
import traceback 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 react.jsx import JSXTransformer # type: ignore
from flask import ( from flask import (
Flask, Flask,
@@ -320,13 +320,13 @@ def valid_username(username: str) -> bool:
return re.match(r"^[a-zA-Z0-9_]+$", username) is not None 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": if type == "card":
return re.match(r"^\d\d\d\d$", pin) is not None return re.match(r"^\d\d\d\d$", pin) is not None
elif type == "arcade": elif type == "arcade":
return re.match(r"^\d\d\d\d\d\d\d\d$", pin) is not None return re.match(r"^\d\d\d\d\d\d\d\d$", pin) is not None
else: else:
return False return False # type: ignore
# Define useful functions for jnija2 # Define useful functions for jnija2
@@ -356,6 +356,8 @@ def navigation() -> Dict[str, Any]:
custom_config = {} custom_config = {}
if g.config.server.allow_raw_ids: if g.config.server.allow_raw_ids:
custom_config["allow_raw_ids"] = True 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. # Look up the logged in user ID.
try: try:
+16 -12
View File
@@ -5,19 +5,23 @@
<form action="{{ url_for('account_pages.register') }}" method=post> <form action="{{ url_for('account_pages.register') }}" method=post>
<p> <p>
To register an account on this network you will need to have played at least {% if custom_config.get("allow_unlinked_signups", False) %}
one credit on a game linked to this network. If you have not done so you cannot If you have played one or more credits on a game linked to this network you can
register an account. Enter the card number and PIN of the card you have used to claim that card when creating your account. Enter the card number which is the
play on this network. 16 digit code found on the back of an e-AMUSEMENT card or displayed in-game when
{% if custom_config.get("allow_raw_ids", False) %} you scan an Amusement IC card. Make sure you use the same PIN as you did when
The card number is the 16 digit code found on the back of an you registered your card in-game. If you haven't played yet, enter the card
e-AMUSEMENT card or displayed in-game when you scan an Amusement number and desired PIN for the card you want to use.
IC card. For convenience, you can also enter the raw 16 digit card
ID usually starting with <code>E004</code>.
{% else %} {% else %}
The card number is the 16 digit code found on the back of an To register an account on this network you will need to have played at least
e-AMUSEMENT card or displayed in-game when you scan an Amusement one credit on a game linked to this network. If you have not done so you cannot
IC card. 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 <code>E004</code>.
{% endif %} {% endif %}
</p> </p>
<dl> <dl>
+50 -24
View File
@@ -8,29 +8,37 @@ database:
user: "bemani" user: "bemani"
# Password of said user. # Password of said user.
password: "bemani" password: "bemani"
# Force the network to read-only mode, refusing to write to the DB # Force the network to read-only mode, refusing to write to the DB except
# except for creating/destroying frontend sessions to enable login. # for creating/destroying frontend sessions to enable login. Set this to
# Set this to False or delete this to run in production mode. # False or delete this to run in production mode. Can be useful when testing
# bug fixes against a production database safely.
read_only: False read_only: False
# Core server settings, required so that the backend knows what to tell games for core # Core server settings, required so that the backend knows what to tell games for core
# routing and server URLs. # routing and server URLs.
server: 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" address: "192.168.0.1"
# Advertised keepalive address, must be globally pingable. Delete # Advertised keepalive address, must be globally pingable. Delete this to
# this to use the address above instead of a unique keepalive address. # use the address above instead of a unique keepalive address.
keepalive: "127.0.0.1" 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 port: 80
# Whether games should connect over HTTPS. # Whether games should connect over HTTPS.
https: False 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: "https://eagate.573.jp"
# URI that users hitting the GET interface will be redirected to. # URI that users hitting the GET interface will be redirected to. Delete this
# Delete this to return an HTTP error instead of redirecting. # to return an HTTP error instead of redirecting.
redirect: "https://eagate.573.jp" 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 enforce_pcbid: False
# How many PCBIDs an arcade owner can grant to themselves on the arcade # 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. # 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 # 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. # cards, users can type in the raw ID as a convenience.
allow_raw_ids: False 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 # 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 # 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 # 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. # Delete this setting to force games to display "Unobtained" instead.
area: "USA" area: "USA"
# Webhook URLs. These allow for game scores from games with scorecard support to be broadcasted to outside services. # Webhook URLs. These allow for game scores from games with scorecard support to be
# Delete this to disable this support. # broadcasted to outside services. Delete this to disable this support.
webhooks: webhooks:
discord: discord:
iidx: iidx:
@@ -59,20 +74,22 @@ webhooks:
pnm: pnm:
- "https://discord.com/api/webhooks/1232122131321321321/eauihfafaewfhjaveuijaewuivhjawueihoi" - "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: assets:
jubeat: jubeat:
emblems: "/directory/where/you/output/emblem/assets" 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: paseli:
# Whether PASELI is enabled on the network. # Whether PASELI is enabled on the network.
enabled: True enabled: True
# Whether infinite PASELI balance is enabled on the network. # Whether infinite PASELI balance is enabled on the network.
infinite: True infinite: True
# Game series to provide support for. Disabling something here hides it from the frontend and makes the backend # Game series to provide support for. Disabling something here hides it from the
# ignore games coming from that series. # frontend and makes the backend ignore games coming from that series.
support: support:
# Bishi Bashi frontend/backend enabled. # Bishi Bashi frontend/backend enabled.
bishi: True bishi: True
@@ -95,11 +112,14 @@ support:
# SDVX frontend/backend enabled. # SDVX frontend/backend enabled.
sdvx: True 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' 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' 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' email: 'nobody@nowhere.com'
# Cache DIR, should point somewhere other than /tmp for production instances that wish # Cache DIR, should point somewhere other than /tmp for production instances that wish
# to use filesystem caching. For memcached, delete this value. # 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 # 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. # instances that wish to use memcached backend. For filesystem caching, delete this value.
memcached_server: 1.2.3.4:5678 memcached_server: 1.2.3.4:5678
# Number of seconds to preserve event logs before deleting them. # Number of seconds to preserve event logs before deleting them. Set to zero or delete
# Set to zero or delete to disable deleting logs. # this to disable deleting logs.
event_log_duration: 2592000 event_log_duration: 2592000
# Whether we log verbosely (full packet request and response) to web server logs or not. # Whether we log verbosely (full packet request and response) to web server logs or not.
verbose: true # Keeping this on is recommended because you can often replay packets found in the logs
# Frontend theme directory where sitewide CSS and favicon should be found. # 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" theme: "default"