From a10c07dc57b79ff5d5c9fb1420fa5328c9fffb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=92=95=E8=B0=B7=E9=85=B1?= Date: Sun, 12 Oct 2025 03:34:38 +0800 Subject: [PATCH] Align session verification logic with osu-web Updated session verification method selection to match osu-web's State.php:36 logic, using SUPPORT_TOTP_VERIFICATION_VER for version checks and prioritizing TOTP when available. Added example environment files for osu-web-master to support local, dusk, and testing setups. --- app/dependencies/user.py | 4 +- app/router/auth.py | 7 +- app/router/v2/session_verify.py | 9 +- osu-web-master/.env.dusk.local.example | 13 + osu-web-master/.env.example | 333 +++++++++++++++++++++++++ osu-web-master/.env.testing.example | 12 + 6 files changed, 371 insertions(+), 7 deletions(-) create mode 100644 osu-web-master/.env.dusk.local.example create mode 100644 osu-web-master/.env.example create mode 100644 osu-web-master/.env.testing.example diff --git a/app/dependencies/user.py b/app/dependencies/user.py index 9d2e75f..efd8139 100644 --- a/app/dependencies/user.py +++ b/app/dependencies/user.py @@ -121,12 +121,12 @@ async def get_client_user( verify_method = await LoginSessionService.get_login_method(user.id, token.id, redis) if verify_method is None: - # 智能选择验证方式(有TOTP优先TOTP) + # 智能选择验证方式(参考 osu-web State.php:36) totp_key = await user.awaitable_attrs.totp_key verify_method = "totp" if totp_key is not None and api_version >= SUPPORT_TOTP_VERIFICATION_VER else "mail" # 设置选择的验证方法到Redis中,避免重复选择 - if api_version >= 20250913: + if api_version >= SUPPORT_TOTP_VERIFICATION_VER: await LoginSessionService.set_login_method(user.id, token.id, verify_method, redis) # 返回符合 osu! API 标准的错误响应 diff --git a/app/router/auth.py b/app/router/auth.py index b2ec619..19cdf94 100644 --- a/app/router/auth.py +++ b/app/router/auth.py @@ -338,8 +338,13 @@ async def oauth_token( # 检查是否为新位置登录 trusted_device = await LoginSessionService.check_trusted_device(db, user_id, ip_address, user_agent, web_uuid) + # 根据 osu-web 逻辑确定验证方法: + # 1. 如果 API 版本支持 TOTP 且用户启用了 TOTP,则始终要求 TOTP 验证(无论是否为信任设备) + # 2. 否则,如果是新设备且启用了邮件验证,则要求邮件验证 + # 3. 否则,不需要验证或自动验证 session_verification_method = None - if settings.enable_totp_verification and totp_key is not None and api_version >= SUPPORT_TOTP_VERIFICATION_VER: + if api_version >= SUPPORT_TOTP_VERIFICATION_VER and settings.enable_totp_verification and totp_key is not None: + # TOTP 验证优先(参考 osu-web State.php:36) session_verification_method = "totp" await LoginLogService.record_login( db=db, diff --git a/app/router/v2/session_verify.py b/app/router/v2/session_verify.py index d1fecc8..1af6731 100644 --- a/app/router/v2/session_verify.py +++ b/app/router/v2/session_verify.py @@ -88,10 +88,9 @@ async def verify_session( try: totp_key: TotpKeys | None = await current_user.awaitable_attrs.totp_key if verify_method is None: - # 智能选择验证方法(参考osu-web实现) + # 智能选择验证方法(参考osu-web实现 State.php:36) # API版本较老或用户未设置TOTP时强制使用邮件验证 - # print(api_version, totp_key) - verify_method = "mail" if api_version < 20240101 or totp_key is None else "totp" + verify_method = "mail" if api_version < SUPPORT_TOTP_VERIFICATION_VER or totp_key is None else "totp" await LoginSessionService.set_login_method(user_id, token_id, verify_method, redis) login_method = verify_method @@ -210,7 +209,9 @@ async def reissue_verification_code( return SessionReissueResponse(success=False, message="当前会话不需要验证") verify_method: str | None = ( - "mail" if api_version < 20250913 else await LoginSessionService.get_login_method(user_id, token_id, redis) + "mail" + if api_version < SUPPORT_TOTP_VERIFICATION_VER + else await LoginSessionService.get_login_method(user_id, token_id, redis) ) if verify_method != "mail": return SessionReissueResponse(success=False, message="当前会话不支持重新发送验证码") diff --git a/osu-web-master/.env.dusk.local.example b/osu-web-master/.env.dusk.local.example new file mode 100644 index 0000000..063966f --- /dev/null +++ b/osu-web-master/.env.dusk.local.example @@ -0,0 +1,13 @@ +APP_KEY= +APP_ENV=testing + +APP_URL=http://nginx:8008 +NOTIFICATION_ENDPOINT=ws://nginx:8008/home/notifications/feed + +DB_DATABASE=osu_test +DB_DATABASE_CHAT=osu_chat_test +DB_DATABASE_MP=osu_mp_test +DB_DATABASE_STORE=osu_store_test +DB_DATABASE_UPDATES=osu_updates_test + +ES_INDEX_PREFIX=test_ diff --git a/osu-web-master/.env.example b/osu-web-master/.env.example new file mode 100644 index 0000000..778932d --- /dev/null +++ b/osu-web-master/.env.example @@ -0,0 +1,333 @@ +# API_THROTTLE_GLOBAL=1200,1,api +# API_THROTTLE_SCORES_DOWNLOAD=10,1,api-scores-download + +# default url for vagrant +APP_URL=http://localhost:8080 +APP_ENV=local +APP_DEBUG=true +APP_KEY= +APP_LOG_LEVEL=debug +# APP_SENTRY=https://... +# APP_SENTRY_ENVIRONMENT= + +# DOCS_URL= + +# clockwork provides local development insights (at /__clockwork/) +# adds a slight performance overhead. +CLOCKWORK_ENABLE=true + +DB_HOST=localhost +DB_DATABASE=osu +DB_USERNAME=osuweb +# DB_PASSWORD= + +# REDIS_HOST=127.0.0.1 +# REDIS_PORT=6379 +# REDIS_DB=0 +# REDIS_PASSWORD= + +# CACHE_REDIS_HOST=127.0.0.1 +# CACHE_REDIS_PORT=6379 +# CACHE_REDIS_DB=0 +# CACHE_REDIS_PASSWORD= + +# MEMCACHED_PERSISTENT_ID= +# MEMCACHED_USERNAME= +# MEMCACHED_PASSWORD= +# MEMCACHED_HOST=127.0.0.1 +# MEMCACHED_PORT=11211 + +OSU_API_KEY= + +# BROADCAST_DRIVER=redis +# CACHE_DRIVER=redis +# SESSION_DRIVER=redis +# SESSION_DOMAIN= +# SESSION_SECURE_COOKIE=false +# SESSION_PREFIX= + +# MAIL_DRIVER=log +# MAIL_HOST= +# MAIL_PORT= +# MAIL_ENCRYPTION= +# MAIL_USERNAME= +# MAIL_PASSWORD= + +SLACK_ENDPOINT=https://myconan.net/null/ +# SHARED_INTEROP_SECRET= + +# STORE_NOTICE= + +# FILESYSTEM_DISK=local + +# BM_PROCESSOR_MIRRORS=1 +# BM_PROCESSOR_THUMBNAILER=http://localhost:4001 +# BM_PROCESSOR_SENTRY= + +# S3_KEY= +# S3_SECRET= +# S3_REGION= +# S3_BUCKET= +# S3_BASE_URL= +# S3_MINI_URL= + +# S3_ENDPOINT= +# S3_USE_PATH_STYLE_ENDPOINT=false + +# S3_CENTRAL_BUCKET_NAME= +# S3_CENTRAL_BUCKET_REGION= +# S3_SOLO_REPLAY_BUCKET=solo-scores-replays + +# S3_AVATAR_KEY= +# S3_AVATAR_SECRET= +# S3_AVATAR_REGION= +# S3_AVATAR_BUCKET= +# S3_AVATAR_BASE_URL= +# AVATAR_CACHE_PURGE_PREFIX= +# AVATAR_CACHE_PURGE_METHOD= +# AVATAR_CACHE_PURGE_AUTHORIZATION_KEY= +# DEFAULT_AVATAR=http://localhost/images/layout/avatar-guest@2x.png + +# QUEUE_DRIVER= +# CAMO_KEY= +# CAMO_PREFIX= + +# ADMIN_FORUM_ID= +# FEATURE_FORUM_ID=4 +# FEATURE_TOPIC_LARGE_STAR_CUTOFF=1000 +# HELP_FORUM_ID=5 +# INITIAL_HELP_FORUM_IDS="5 47 85" +# ISSUE_FORUM_IDS= +# FORUM_POST_MINIMUM_PLAYS=200 +# BEATMAP_DESCRIPTION_FORUM_ID=6 +# DOUBLE_POST_ALLOWED_FORUM_IDS="52 68 84 114" + +PUSHER_APP_ID= +PUSHER_KEY= +PUSHER_SECRET= + +# PASSPORT_KEY_PATH=/secure/osu.ppy.sh/oauth + +# ENCHANT_ID= + +# GITHUB_TOKEN= + +# GitHub client for users to associate their GitHub accounts +# Use "/home/account/github-users/callback" for the "Authorization callback URL" field on GitHub +# GITHUB_CLIENT_ID= +# GITHUB_CLIENT_SECRET= + +# DATADOG_ENABLED=true +# DATADOG_PREFIX=osu.web +# DATADOG_API_KEY= +# DATADOG_APP_KEY= +# DATADOG_HOST=https://app.datadoghq.com +# DATADOG_STATSD_HOST=localhost +# DATADOG_STATSD_PORT=8125 +# DATADOG_STATSD_SOCKET= + +# LANDING_VIDEO_URL= + +# FEATURED_UPDATE_STREAM= +# UPDATE_STREAMS= +# CHANGELOG_CHART_DAYS= +# CHANGELOG_BUILD_HISTORY_INTERVAL= +# CHANGELOG_GITHUB_TOKEN= + +# SUPER_FRIENDLY='3' + +PAYMENT_SANDBOX=true + +SHOPIFY_DOMAIN= +SHOPIFY_STOREFRONT_TOKEN= +SHOPIFY_WEBHOOK_KEY= + +STORE_NOTIFICATION_CHANNEL=test +STORE_NOTIFICATIONS_QUEUE=store-notifications +STORE_STALE_DAYS= + +PAYPAL_CLIENT_ID= +PAYPAL_CLIENT_SECRET= +PAYPAL_MERCHANT_ID= +PAYPAL_NO_SHIPPING_EXPERIENCE_PROFILE_ID= +PAYPAL_URL=https://www.sandbox.paypal.com/cgi-bin/webscr + +XSOLLA_API_KEY= +XSOLLA_MERCHANT_ID= +XSOLLA_PROJECT_ID= +XSOLLA_SECRET_KEY= + +OSU_RUNNING_COST= + +CLIENT_CHECK_VERSION=false +# CLIENT_USER_AGENT=osu! +# DEFAULT_BUILD_ID=0 + +# SEARCH_MINIMUM_LENGTH=2 + +# BEATMAPS_DIFFICULTY_CACHE_SERVER_URL=http://localhost:5001 +# BEATMAPS_OWNERS_MAX=10 +# BEATMAPSET_DISCUSSION_KUDOSU_PER_USER=10 +# BEATMAPSET_GUEST_ADVANCED_SEARCH=0 +# BEATMAPSET_MAXIMUM_DISQUALIFIED_RANK_PENALTY_DAYS=7 +# BEATMAPSET_REQUIRED_HYPE=5 +# BEATMAPSET_USER_WEEKLY_HYPE=3 +# BEATMAPSET_USER_DAILY_NOMINATIONS=10 +# BEATMAPSET_USER_DOWNLOAD_LIMIT_HOURLY=10 +# BEATMAPSET_USER_DOWNLOAD_LIMIT_HOURLY_SUPPORTER=20 +# BEATMAPSET_USER_FAVOURITE_LIMIT=100 +# BEATMAPSET_USER_FAVOURITE_LIMIT_SUPPORTER=1000 + +# Nominations required for a Beatmapset to be qualified. For hybrid Beatmapsets this is the nominations required for the main ruleset. +# BEATMAPSET_REQUIRED_NOMINATIONS=2 + +# BAN_PERSIST_DAYS=28 + +# ES_HOST=localhost:9200 +# ES_SOLO_SCORES_HOST=localhost:9200 +# ES_INDEX_PREFIX= +# ES_CLIENT_TIMEOUT=5 +# ES_CLIENT_CONNECT_TIMEOUT=0.5 +# ES_SEARCH_TIMEOUT=5s + +# {prefix}{filename}.png with {filename} the achievement slug as stored in database. +# USER_ACHIEVEMENT_ICON_PREFIX=https://assets.ppy.sh/user-achievements/ + +## Limits for chat, throttles after a user sends more than CHAT_*_LIMIT messages in CHAT_*_WINDOW seconds +# CHAT_CHANNEL_LIMIT=10000 +# CHAT_PUBLIC_LIMIT=1 +# CHAT_PUBLIC_WINDOW=1 +# CHAT_PRIVATE_LIMIT=1 +# CHAT_PRIVATE_WINDOW=1 +# CHAT_MESSAGE_LENGTH_LIMIT=450 +# CHAT_PUBLIC_BACKLOG_LIMIT_HOURS=24 + +# ALLOW_REGISTRATION=true +# REGISTRATION_MODE_CLIENT=true +# REGISTRATION_MODE_WEB=false + +# USER_ALLOW_EMAIL_LOGIN=true +# USER_BYPASS_VERIFICATION=false +# USER_POST_ACTION_VERIFICATION=true +# USER_MAX_LOGIN_ATTEMPTS=10 +# USER_MIN_PLAYS_FOR_POSTING=10 +# USER_MIN_PLAYS_ALLOW_VERIFIED_BYPASS=true +# space delimited, list of groups (the identifier) which user can be renamed when inactive +# USER_ALLOWED_RENAME_GROUPS="default" + +# USER_MAX_FOLLOWS=5000 +# USER_MAX_FRIENDS=250 +# USER_MAX_FRIENDS_SUPPORTER=500 +# USER_MAX_MULTIPLAYER_DURATION=14 +# USER_MAX_MULTIPLAYER_DURATION_SUPPORTER=63 +# USER_MAX_MULTIPLAYER_ROOMS=1 +# USER_MAX_MULTIPLAYER_ROOMS_SUPPORTER=5 + +# USER_MAX_SCORE_PINS=10 +# USER_MAX_SCORE_PINS_SUPPORTER=50 + +# the content is in markdown format +# USER_PROFILE_SCORES_NOTICE= + +# MULTIPLAYER_MAX_ATTEMPTS_LIMIT=128 +# MULTIPLAYER_ROOM_CLOSE_GRACE_PERIOD_MINUTES=5 + +# NOTIFICATION_QUEUE=notification +# NOTIFICATION_REDIS_HOST=127.0.0.1 +# NOTIFICATION_REDIS_PORT=6379 +# NOTIFICATION_REDIS_DB=0 +# NOTIFICATION_REDIS_PASSWORD= +# NOTIFICATION_SERVER_LISTEN_HOST=127.0.0.1 +# NOTIFICATION_SERVER_LISTEN_PORT=3000 +# NOTIFICATION_ENDPOINT=/home/notifications/feed +# NOTIFICATION_CLEANUP_KEEP_DAYS=180 +# NOTIFICATION_CLEANUP_MAX_DELETE=50000 + +# The open source bounty info page/form url +# OS_BOUNTY_URL=http://example.com/bounty_form + +# OAUTH_MAX_USER_CLIENTS=1 + +# TEAM_CREATE_REQUIRE_SUPPORTER=false +# TEAM_MAX_MEMBERS=40 + +# USER_REPORT_NOTIFICATION_ENDPOINT_CHEATING= +# default if nothing specified for specific type +# USER_REPORT_NOTIFICATION_ENDPOINT_MODERATION= + +# USER_REPORT_NOTIFICATION_ENDPOINT_BEATMAPSET= +# USER_REPORT_NOTIFICATION_ENDPOINT_BEATMAPSET_DISCUSSION= +# USER_REPORT_NOTIFICATION_ENDPOINT_CHAT= +# USER_REPORT_NOTIFICATION_ENDPOINT_COMMENT= +# USER_REPORT_NOTIFICATION_ENDPOINT_FORUM= +# USER_REPORT_NOTIFICATION_ENDPOINT_USER= + +# LOG_CHANNEL=single + +# WIKI_BRANCH=master +# WIKI_REPOSITORY=osu-wiki +# WIKI_USER=ppy + +# BEATMAPSET_DISCUSSION_REVIEW_MAXIMUM_BLOCKS=1 +# BEATMAPSET_DISCUSSION_REVIEW_MINIMUM_ISSUES=1 + +# PAGINATION_MAX_COUNT=10000 + +## Limits for the allowed number of simultaneous beatmapset uploads (displayed on the support page: /home/support) +# BEATMAPSET_UPLOAD_ALLOWED=4 +# BEATMAPSET_UPLOAD_BONUS_PER_RANKED=1 +# BEATMAPSET_UPLOAD_BONUS_PER_RANKED_MAX=2 +# BEATMAPSET_UPLOAD_ALLOWED_SUPPORTER=8 +# BEATMAPSET_UPLOAD_BONUS_PER_RANKED_SUPPORTER=1 +# BEATMAPSET_UPLOAD_BONUS_PER_RANKED_MAX_SUPPORTER=12 + +# CAPTCHA_THRESHOLD= +# TURNSTILE_SITE_KEY= +# TURNSTILE_SECRET_KEY= + +# TWITCH_CLIENT_ID= +# TWITCH_CLIENT_SECRET= + +# SCORES_ES_CACHE_DURATION= +# SCORES_PROCESSING_QUEUE=osu-queue:score-statistics +# SCORES_SUBMISSION_ENABLED=1 +# SCORE_INDEX_MAX_ID_DISTANCE=10_000_000 + +# BANCHO_BOT_USER_ID= + +# OCTANE_LOCAL_CACHE_EXPIRE_SECOND=60 +# OCTANE_LOCAL_CACHE_RESET_REQUESTS=100 + +# TRUSTED_PROXIES= + +# IS_DEVELOPMENT_DEPLOY=true + +# OSU_URL_LAZER_ANDROID='https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk' +# OSU_URL_LAZER_IOS='/home/testflight' +# OSU_URL_LAZER_LINUX_X64='https://github.com/ppy/osu/releases/latest/download/osu.AppImage' +# OSU_URL_LAZER_MACOS_AS='https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip' +# OSU_URL_LAZER_OTHER='https://github.com/ppy/osu/#running-osu' +# OSU_URL_LAZER_WINDOWS_X64='https://github.com/ppy/osu/releases/latest/download/install.exe' +# OSU_URL_LAZER_INFO= +# OSU_URL_MENU_CONTENT_JSON=https://assets.ppy.sh/menu-content.json +# OSU_URL_USER_RESTRICTION=/wiki/Help_centre/Account_restrictions + +# USER_COUNTRY_CHANGE_MAX_MIXED_MONTHS=2 +# USER_COUNTRY_CHANGE_MIN_MONTHS=6 + +# USER_INACTIVE_DAYS_VERIFICATION=180 +# USER_INACTIVE_FORCE_PASSWORD_RESET=false + +# COUNTRY_PERFORMANCE_USER_COUNT=1000 +# COUNTRY_PERFORMANCE_WEIGHTING_FACTOR=0.99 +# TEAM_PERFORMANCE_USER_COUNT=48 +# TEAM_PERFORMANCE_WEIGHTING_FACTOR=0.96 + +# BEATMAP_TAGS_CACHE_DURATION=60 +# BEATMAP_TAGS_MIN_VOTES_DISPLAY=5 +# BEATMAP_TAGS_TOP_COUNT=50 + +# OSU_SENTRY_MIN_LOG_DURATION_MS=500 +# SENTRY_TRACES_SAMPLE_RATE= + +# TOTP_ISSUER_NAME=osu!dev diff --git a/osu-web-master/.env.testing.example b/osu-web-master/.env.testing.example new file mode 100644 index 0000000..1e15950 --- /dev/null +++ b/osu-web-master/.env.testing.example @@ -0,0 +1,12 @@ +DB_DATABASE_CHAT=osu_chat_test +DB_DATABASE_MP=osu_mp_test +DB_DATABASE_STORE=osu_store_test +DB_DATABASE_UPDATES=osu_updates_test +DB_DATABASE_CHARTS=osu_charts_test + +# match with docker-compose.yml +DB_DATABASE=osu_test +ES_INDEX_PREFIX=test_ +SCHEMA=test + +PAYMENT_SANDBOX=true