player/src/auth/ticketExchange.js
МИН 87444ee2c8 Initial public release: Rublox Player v1.0
Open-source web player for Rublox games, dual-licensed under
AGPL-3.0 + Commercial.

Highlights:
- Babylon.js 7 + React 18 + Vite 5 stack
- Self-contained engine (~46k lines): BlockManager, ModelManager,
  PlayerController, ScriptSandboxWorker, MultiplayerSync, 30+ GD
  gamemodes
- Configurable backend via VITE_API_BASE and friends — works against
  staging (dev-api.rublox.pro) out of the box
- Standalone mode (VITE_STANDALONE=true) loads a bundled sample game
  for first-run without any backend
- Full docs: README, ARCHITECTURE, CONTRIBUTING, SECURITY, CHANGELOG
- Lint + format scaffolding (ESLint + Prettier + EditorConfig)
- Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md
- Issue templates: bug_report, feature_request, security_disclosure

Removed before public release:
- frontend_deploy.py (contained production SSH credentials)
- ~27 admin endpoints (kept in private repo)
- Hard-coded internal URLs and IPs
- All previous git history (clean repo init)
2026-05-27 23:04:04 +03:00

105 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ticketExchange — обмен одноразового ticket на постоянный JWT.
//
// Поток:
// rublox.pro / Майнкрафтия → POST /api-user/api/v1/auth/play-ticket → { ticket }
// браузер открывает player.rublox.pro/<id>#ticket=<ticket>
// плеер → POST /api-user/api/v1/auth/redeem-ticket { ticket } → { jwt, user }
// плеер сохраняет JWT в localStorage["player_jwt"]
//
// На стороне нашего плеера здесь ТОЛЬКО redeem-сторона.
// Сторону play-ticket делают rublox.pro и Майнкрафтия (Этапы 5 и 6).
import axios from 'axios';
import { USER_addres } from '../api/API';
const JWT_KEY = 'player_jwt';
/**
* Обменять ticket на JWT + user-данные.
* Возвращает { jwt, user } или кидает ошибку (которую вызывающий ловит
* и показывает в LoadingScreen).
*/
export async function redeemTicket(ticket) {
const r = await axios.post(
`${USER_addres}/api/v1/auth/redeem-ticket`,
{ ticket },
{ timeout: 15000 },
);
return r.data;
}
// «Зеркало» в Authorization — это ключ, который читают engine, скрипты
// уровней (game.save.*), KubikonBugReport, KubikonChatPanel и ещё ~10
// мест в скопированном 1-в-1 коде. Чтобы НЕ править engine (по правилу
// «1-в-1, ничего не ломать»), JWT кладётся в ОБА ключа:
// - player_jwt: новый канон плеера (читают auth/PlayerAuth.jsx,
// api/Kubikon3DService.js)
// - Authorization: совместимость с Майнкрафтия-кодом из engine
// (читают GameRuntime._saveBaseUrl, GdPlayerModeSkin, GdPlayerTrail,
// GdPortalArch, KubikonChatPanel, KubikonComments, KubikonBugReport,
// KubikonPlayer.jsx userId-getter и многое другое).
//
// Это разово при логине, читается из обоих мест — синхронизация
// гарантирована.
const AUTH_KEY_MIRROR = 'Authorization';
export function saveJWT(jwt) {
try {
localStorage.setItem(JWT_KEY, jwt);
localStorage.setItem(AUTH_KEY_MIRROR, jwt);
} catch (e) {}
}
export function getJWT() {
try { return localStorage.getItem(JWT_KEY); } catch (e) { return null; }
}
export function clearJWT() {
try {
localStorage.removeItem(JWT_KEY);
localStorage.removeItem(AUTH_KEY_MIRROR);
} catch (e) {}
}
/**
* Достать ticket из window.location.hash. Возвращает hex-строку или null.
* Hash чистится отдельно (после успешного redeem) через history.replaceState,
* чтобы юзер не мог поделиться URL с одноразовым ticket'ом (даже после
* его сжигания это просто плохой UX).
*/
export function readTicketFromHash() {
if (typeof window === 'undefined') return null;
const m = /(?:^|[#&])ticket=([0-9a-fA-F]+)/.exec(window.location.hash || '');
return m ? m[1] : null;
}
/**
* Достать team_jwt из window.location.hash. Используется когда модератор
* с team.rublox.pro открывает draft/review-игру в плеере для проверки.
*
* Поток: team-фронт берёт свой team_jwt из localStorage и формирует
* URL вида `https://player.rublox.pro/<id>#team_jwt=<jwt>`. Мы кладём
* этот jwt прямо в localStorage плеера (без redeem-flow) — team_jwt и
* user_jwt подписаны одним секретом, user-сервис их обрабатывает
* одинаково. На стороне storys get_project ищет moderator-роль через
* team-flask (см. team_mod_auth.py) и пропускает к draft/review-проектам.
*
* Безопасность: team_jwt и так уже выдаётся модератору после входа в
* team.rublox.pro; передача его в hash другому поддомену того же origin
* (rublox.pro) не повышает риск. Hash не уходит в логи серверов.
*/
export function readTeamJwtFromHash() {
if (typeof window === 'undefined') return null;
// JWT-формат: header.payload.signature — три blob'а из base64url, точки.
const m = /(?:^|[#&])team_jwt=([A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)/
.exec(window.location.hash || '');
return m ? m[1] : null;
}
export function clearTicketFromUrl() {
if (typeof window === 'undefined' || !window.history?.replaceState) return;
try {
window.history.replaceState(null, '', window.location.pathname + window.location.search);
} catch (e) {}
}