Add a setting to allow raw card IDs to be inserted anywhere that accepts a card ID on the frontend.

This commit is contained in:
Jennifer Taylor
2026-08-20 23:16:38 +00:00
parent 42149275c6
commit 0675ca9939
7 changed files with 41 additions and 13 deletions

View File

@@ -9,7 +9,7 @@ from bemani.common.constants import (
BroadcastConstants,
RegionConstants,
)
from bemani.common.card import CardCipher, CardCipherException
from bemani.common.card import CardCipher, CardCipherException, decode_user_provided
from bemani.common.decorators import debugonly
from bemani.common.id import ID
from bemani.common.aes import AESCipher
@@ -41,5 +41,6 @@ __all__ = [
"InvalidOffsetException",
"cache",
"debugonly",
"decode_user_provided",
"format_recovery_link",
]

View File

@@ -1,7 +1,10 @@
from typing import Dict, Final, List
from typing import Dict, Final, List, TYPE_CHECKING
from Crypto.Cipher import DES3
if TYPE_CHECKING:
from bemani.data import Config
class CardCipherException(Exception):
pass
@@ -174,3 +177,17 @@ class CardCipher:
checksum = sum(divmod(checksum, 0x20))
return checksum
def decode_user_provided(config: "Config", cardid: str) -> str:
# First, try to decode the card ID as if it was encoded.
try:
return CardCipher.decode(cardid)
except CardCipherException:
if not config.server.allow_raw_ids:
raise
# Now, assume it is already raw. We round trip it because
# this will check for ID validity even with the raw ID.
enc = CardCipher.encode(cardid)
return CardCipher.decode(enc)

View File

@@ -79,6 +79,10 @@ class Server:
def pcbid_self_grant_limit(self) -> int:
return int(self.__config.get("server", {}).get("pcbid_self_grant_limit", 0))
@property
def allow_raw_ids(self) -> bool:
return bool(self.__config.get("server", {}).get("allow_raw_ids", False))
@property
def region(self) -> int:
region = int(self.__config.get("server", {}).get("region", RegionConstants.USA))

View File

@@ -9,7 +9,7 @@ from flask import (
render_template,
)
from bemani.common import CardCipher, CardCipherException, AESCipher, Time
from bemani.common import CardCipher, CardCipherException, AESCipher, Time, decode_user_provided
from bemani.frontend.app import (
loginrequired,
loginprohibited,
@@ -114,7 +114,7 @@ def recover() -> Response:
elif card_number:
# First, try to convert the card to a valid E004 ID
try:
cardid = CardCipher.decode(card_number)
cardid = decode_user_provided(g.config, card_number)
except CardCipherException:
error("Invalid card number!")
return recover_display(username, token, card_number)
@@ -197,7 +197,7 @@ def register() -> Response:
# First, try to convert the card to a valid E004 ID
try:
cardid = CardCipher.decode(card_number)
cardid = decode_user_provided(g.config, card_number)
except CardCipherException:
error("Invalid card number!")
return register_display(card_number, username, email)
@@ -335,7 +335,7 @@ def addcard() -> Dict[str, Any]:
# Grab card, convert it
card = request.get_json()["card"]
try:
cardid = CardCipher.decode(card)
cardid = decode_user_provided(g.config, card)
except CardCipherException:
raise Exception("Invalid card number!")
@@ -361,7 +361,7 @@ def removecard() -> Dict[str, Any]:
# Grab card, convert it
card = request.get_json()["card"]
try:
cardid = CardCipher.decode(card)
cardid = decode_user_provided(g.config, card)
except CardCipherException:
raise Exception("Invalid card number!")

View File

@@ -11,6 +11,7 @@ from bemani.common import (
ValidatedDict,
Profile,
ID,
decode_user_provided,
format_recovery_link,
)
from bemani.data import Arcade, Machine, User, UserID, News, Event, Server, Client
@@ -816,7 +817,7 @@ def removecard() -> Dict[str, Any]:
# Grab card, convert it
card = request.get_json()["card"]
try:
cardid = CardCipher.decode(card)
cardid = decode_user_provided(g.config, card)
except CardCipherException:
raise Exception("Invalid card number!")
@@ -839,7 +840,7 @@ def addcard() -> Dict[str, Any]:
# Grab card, convert it
card = request.get_json()["card"]
try:
cardid = CardCipher.decode(card["number"])
cardid = decode_user_provided(g.config, card["number"])
except CardCipherException:
raise Exception("Invalid card number!")
@@ -1098,7 +1099,7 @@ def removeusercard(userid: int) -> Dict[str, Any]:
# Grab card, convert it
card = request.get_json()["card"]
try:
cardid = CardCipher.decode(card)
cardid = decode_user_provided(g.config, card)
except CardCipherException:
raise Exception("Invalid card number!")
user = g.data.local.user.get_user(userid)
@@ -1153,7 +1154,7 @@ def addusercard(userid: int) -> Dict[str, Any]:
# Grab card, convert it
card = request.get_json()["card"]
try:
cardid = CardCipher.decode(card)
cardid = decode_user_provided(g.config, card)
except CardCipherException:
raise Exception("Invalid card number!")
user = g.data.local.user.get_user(userid)

View File

@@ -4,11 +4,11 @@ from flask import Blueprint, request, Response, abort, url_for
from bemani.backend.base import Base
from bemani.common import (
CardCipher,
CardCipherException,
ValidatedDict,
GameConstants,
RegionConstants,
decode_user_provided,
)
from bemani.data import Arcade, ArcadeID, Event, Machine
from bemani.frontend.app import loginrequired, jsonify, render_react, valid_pin
@@ -186,7 +186,7 @@ def addbalance(arcadeid: int) -> Dict[str, Any]:
raise Exception("You don't own this arcade, refusing to update!")
try:
cardid = CardCipher.decode(card)
cardid = decode_user_provided(g.config, card)
userid = g.data.local.user.from_cardid(cardid)
except CardCipherException:
userid = None

View File

@@ -36,6 +36,11 @@ server:
# page. Note that this setting is irrelevant if PCBID enforcing is off.
# Set to 0 or delete this setting to disable self-granting PCBIDs.
pcbid_self_grant_limit: 0
# Whether frontend text boxes that accept a Card ID will accept a raw ID
# starting with E004 or 0XXX. With this enabled, while the frontend will
# 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
# 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