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

View File

@@ -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))

View File

@@ -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

View File

@@ -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:

View File

@@ -5,19 +5,23 @@
<form action="{{ url_for('account_pages.register') }}" method=post>
<p>
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 <code>E004</code>.
{% 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 <code>E004</code>.
{% endif %}
</p>
<dl>

View File

@@ -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"