Logowanie przez GameVox
Pozwól użytkownikom logować się do twojej witryny lub aplikacji ich kontem GameVox. To standardowy przepływ OAuth2 z kodem autoryzacji (authorization code), zbudowany tak jak na Discordzie, i nie wymaga bota: twoja aplikacja prosi o dostęp do części konta użytkownika, użytkownik się zgadza, a twój serwer dostaje token.
Do czego to służy
- Logowanie i łączenie kont. Dowiesz się, kim jest użytkownik (stały identyfikator użytkownika GameVox, nazwa użytkownika, awatar), a za jego zgodą także poznasz jego zweryfikowany adres e-mail.
- Wybór serwera i panele. Z zakresem
guildswidzisz, na jakich serwerach jest użytkownik i gdzie może zarządzać ustawieniami. - Nie służy do działania w imieniu użytkownika. Token użytkownika nie może wysyłać wiadomości, dołączać do kanałów głosowych ani niczego zmieniać w GameVox. Do wszystkiego, co działa wewnątrz GameVox, potrzebny jest bot.
Zanim zaczniesz
- Utwórz aplikację na developers.gamevox.com. Client ID z jej karty OAuth2 to twój
client_id. - W zakładce OAuth2 dodaj wszystkie URI przekierowania, których będzie używać twoja aplikacja (maksymalnie 10). Muszą zaczynać się od
https://(podczas developmentu takżehttp://localhost/http://127.0.0.1) i nie mogą zawierać#fragment. Wysyłany przez ciebieredirect_urimusi dokładnie pasować do jednego z nich, łącznie z parametrami zapytania. - Skopiuj client secret. Jest pokazywany raz: przy tworzeniu aplikacji lub resetowaniu sekretu. Trzymaj go na swoim serwerze: wymaga go każde żądanie tokena.
Endpointy
GET https://gamevox.com/oauth2/authorize/
POST https://app-api.gamevox.com/api/oauth2/token
POST https://app-api.gamevox.com/api/oauth2/token/revoke
GET https://app-api.gamevox.com/api/users/@me
GET https://app-api.gamevox.com/api/users/@me/guilds
GET https://app-api.gamevox.com/api/oauth2/@me
PUT https://api.gamevox.com/api/users/@me/applications/{application.id}/role-connection Zachowaj ukośnik na końcu /oauth2/authorize/. Endpointy tokenów i użytkownika odpowiadają też pod /api/v10/, więc biblioteki, które dodają wersję do ścieżki, działają bez zmian. Połączenia ról (role connections) działają pod api.gamevox.com, a nie app-api.
1. Przekieruj użytkownika do GameVox
Przekieruj przeglądarkę na URL autoryzacji (zwykłą nawigacją, nie w wyskakującym oknie ani w iframe):
https://gamevox.com/oauth2/authorize/
?response_type=code
&client_id=123456789012345678
&scope=identify%20email
&redirect_uri=https%3A%2F%2Fyourapp.example.com%2Fauth%2Fgamevox
&state=Zx81kq0vT2 | Parametr | Wymagany | Opis |
|---|---|---|
response_type | Tak | Zawsze code. |
client_id | Tak | Client ID z karty OAuth2 twojej aplikacji. |
scope | Tak | Rozdzielone spacjami (w URL zakodowanymi jako %20 lub +). Zobacz Zakresy. Pomiń bot: z bot ten URL staje się instalacją na serwerze. |
redirect_uri | Tak | Jeden z twoich zarejestrowanych URI przekierowania, dokładnie w tej samej postaci. |
state | Nie | Niemożliwa do odgadnięcia wartość powiązana z sesją użytkownika (do 1024 znaków). Wraca bez zmian; sprawdzaj ją, aby chronić się przed fałszowaniem żądań między witrynami (CSRF). |
code_challenge | Nie | Opcjonalny challenge PKCE, od 43 do 128 znaków. |
code_challenge_method | Nie | S256, gdy wysyłasz challenge. plain jest odrzucane. |
prompt | Nie | none pomija ekran zgody, jeśli użytkownik już zatwierdził twoją aplikację dla wszystkich tych zakresów. Jeśli prosisz o coś nowego, ekran i tak się pojawi. |
Co widzi użytkownik
- Ekran zgody na gamevox.com z nazwą, ikoną i opisem twojej aplikacji, kontem, na które użytkownik jest zalogowany, zrozumiałą listą danych, do których dostaniesz dostęp, oraz witryną, na którą zostanie przekierowany.
- Jeśli użytkownik nie jest zalogowany, przycisk „Log in” prowadzi go przez aplikację webową GameVox i od razu z powrotem na ten sam ekran.
- Linki do twojego regulaminu i polityki prywatności pojawiają się pod przyciskami, jeśli ustawisz je w zakładce Informacje ogólne.
2. Obsłuż callback
Gdy użytkownik kliknie „Authorize”, GameVox przekieruje go na twój URI przekierowania z kodem:
https://yourapp.example.com/auth/gamevox?code=3vR0b...&state=Zx81kq0vT2 Jeśli zamiast tego kliknie „Cancel”:
https://yourapp.example.com/auth/gamevox?error=access_denied
&error_description=The+user+denied+the+request&state=Zx81kq0vT2 - Odrzuć callback, jeśli
statenie zgadza się z wartością wydaną dla tej przeglądarki. - Kod wygasa po 10 minutach i działa tylko raz. Wymień go od razu, ze swojego serwera.
3. Wymień kod na tokeny
Ze swojego serwera wyślij POST z treścią zakodowaną jako formularz (application/x-www-form-urlencoded, nie JSON) na endpoint tokenów:
curl -X POST https://app-api.gamevox.com/api/oauth2/token \
-u "123456789012345678:CS.your-client-secret" \
-d grant_type=authorization_code \
-d code=3vR0b... \
-d redirect_uri=https://yourapp.example.com/auth/gamevox {
"access_token": "GVO.mJ4c...",
"token_type": "Bearer",
"expires_in": 604800,
"refresh_token": "GVR.9tQe...",
"scope": "identify email"
} - Uwierzytelnij się przez HTTP Basic (
client_id:client_secret) jak powyżej albo wyślijclient_idiclient_secretjako pola formularza. redirect_urimusi mieć tę samą wartość, którą wysłano w kroku 1. Jeśli używasz PKCE, dodajcode_verifier.- Odpowiedzi zawierają
Cache-Control: no-store. Nigdy nie umieszczaj client secret ani tokenów w kodzie działającym w przeglądarce ani w adresach URL.
4. Wywołuj API
Wysyłaj token dostępu jako Authorization: Bearer. GET /users/@me wymaga zakresu identify:
curl https://app-api.gamevox.com/api/users/@me \
-H "Authorization: Bearer GVO.mJ4c..." {
"id": "1557709980780474368",
"gamevox_id": "edf412bc-a7b8-4f2a-a810-8b9eb8c02462",
"username": "alice",
"discriminator": "0",
"global_name": "alice",
"avatar": null,
"gamevox_avatar_url": "https://...",
"email": "alice@example.com",
"verified": true
} idto stały identyfikator użytkownika w GameVox. Wiąż z nim rekordy kont, bo nazwy użytkowników mogą się zmieniać.gamevox_idto ten sam użytkownik w natywnej postaci UUID GameVox.emailiverifiedsą obecne tylko z zakresememaili tylko wtedy, gdy użytkownik ma zapisany adres e-mail. GameVox przechowuje wyłącznie zweryfikowane adresy, więcverifiedma zawsze wartośćtrue, gdy ustawiony jestemail.avatarma zawsze wartośćnull(biblioteki Discorda zbudowałyby z niego link do CDN Discorda). Używajgamevox_avatar_url; to pole jest pomijane, gdy użytkownik nie ma zdjęcia profilowego.
Serwery użytkownika
GET /users/@me/guilds wymaga zakresu guilds:
[
{ "id": "1557445672742031360", "name": "Raid Night", "icon": null,
"owner": true, "permissions": "2251799813685247", "features": [] },
{ "id": "1557445672742031361", "name": "Guild Hall", "icon": null,
"owner": false, "permissions": "1056", "features": [] }
] permissionsto podsumowanie do wybierania serwera, a nie pełne wyliczenie: wszystkie bity (łącznie zADMINISTRATOR) dla właściciela i administratorów,MANAGE_GUILD(32) plusVIEW_CHANNEL(1024) tam, gdzie użytkownik może edytować ustawienia serwera, iVIEW_CHANNELdla wszystkich pozostałych.- Do
limitserwerów na odpowiedź (od 1 do 200, domyślnie 200), od najstarszego. Stronicuj, ustawiającafterlubbeforenaidserwera z poprzedniej strony, tak jak na Discordzie.
Bieżąca autoryzacja
GET /oauth2/@me działa z dowolnym zakresem i mówi, do której aplikacji należy token, co mu przyznano i kiedy wygasa. user jest dołączany przy zakresie identify.
{
"application": { "id": "123456789012345678", "name": "RaidPlanner", ... },
"scopes": ["identify", "email"],
"expires": "2026-10-15T15:03:20Z",
"user": { "id": "1557709980780474368", "username": "alice", ... }
} Połączone role
Z zakresem role_connections.write możesz zapisać, jakie konto użytkownik ma po twojej stronie. Zwróć uwagę na host: api.gamevox.com.
curl -X PUT \
https://api.gamevox.com/api/users/@me/applications/123456789012345678/role-connection \
-H "Authorization: Bearer GVO.mJ4c..." \
-H "Content-Type: application/json" \
-d '{"platform_name": "RaidPlanner", "platform_username": "ShadowMage", "metadata": {}}' Użytkownik widzi to w Settings > Authorized Apps jako „Linked as ShadowMage on RaidPlanner”. Klucze metadata muszą pochodzić z metadanych połączeń ról zdefiniowanych w twojej aplikacji. GET na ten sam URL odczytuje zapisane dane.
Odświeżanie
Tokeny dostępu są ważne przez 7 dni (expires_in jest podawane w sekundach). Nową parę uzyskasz za pomocą tokena odświeżania:
curl -X POST https://app-api.gamevox.com/api/oauth2/token \
-u "123456789012345678:CS.your-client-secret" \
-d grant_type=refresh_token \
-d refresh_token=GVR.9tQe... - Tokeny odświeżania są rotowane. Odpowiedź zawiera nowy token odświeżania, a stary token odświeżania i jego token dostępu natychmiast przestają działać, więc zapisz nową parę, zanim jej użyjesz.
- Możesz wysłać
scope, aby zawęzić nowy token do mniejszej liczby zakresów. Prośba o zakres, którego użytkownik nigdy nie przyznał, kończy się błędeminvalid_scope. - Nieużywany token odświeżania wygasa po 90 dniach. Każdy użytkownik może mieć najwyżej 25 aktywnych par tokenów na aplikację; utworzenie 26. pary usuwa najstarszą.
Unieważnianie (wylogowanie)
Unieważnij tokeny, gdy użytkownik wyloguje się z twojej aplikacji. Dowolny z dwóch tokenów pary unieważnia całą parę. Dla prawidłowego klienta endpoint zawsze odpowiada 200, niezależnie od tego, czy token istniał.
curl -X POST https://app-api.gamevox.com/api/oauth2/token/revoke \
-u "123456789012345678:CS.your-client-secret" \
-d token=GVO.mJ4c... PKCE
Opcjonalne, ale zalecane, jeśli URL autoryzacji może powstawać w miejscu, gdzie ktoś inny mógłby go zobaczyć. GameVox obsługuje tylko S256, a żądanie tokena nadal wymaga client secret: nie ma klientów publicznych bez sekretu.
// Before redirecting: keep the verifier with the user's pending login.
const verifier = base64url(crypto.randomBytes(32));
const challenge = base64url(crypto.createHash('sha256').update(verifier).digest());
// authorize URL: ...&code_challenge=CHALLENGE&code_challenge_method=S256
// token request: ...&code_verifier=VERIFIER (client secret still required) Zakresy
| Zakres | Co umożliwia twojej aplikacji |
|---|---|
identify | Odczyt identyfikatora, nazwy i awatara użytkownika: GET /users/@me oraz user w GET /oauth2/@me. |
email | Dodaje zweryfikowany adres e-mail użytkownika do /users/@me. |
guilds | Odczyt listy serwerów użytkownika: GET /users/@me/guilds. |
role_connections.write | Odczyt i zapis danych połączonej roli użytkownika dla twojej aplikacji. |
Każdy inny zakres, w tym connections, guilds.join i applications.commands bez bot, jest odrzucany na ekranie zgody z odpowiednim komunikatem.
Błędy
Endpointy tokenów i unieważniania zwracają błędy w standardowym formacie OAuth2, {"error": "...", "error_description": "..."}, więc biblioteki OAuth odczytują je bez dodatkowej obsługi:
| error | HTTP | Znaczenie |
|---|---|---|
invalid_client | 401 | Nieprawidłowy client ID lub secret, usunięta aplikacja albo zawieszone konto właściciela aplikacji. |
invalid_grant | 400 | Kod lub token odświeżania jest nieznany, wygasł, został już użyty, został wydany innej aplikacji albo nie zgadza się jego redirect_uri lub code_verifier. Zwracany także po tym, jak użytkownik cofnie autoryzację twojej aplikacji. |
invalid_request | 400 | Brakuje wymaganego pola albo treść nie jest zakodowana jako formularz. |
invalid_scope | 400 | Odświeżenie prosi o zakres, którego nie obejmuje pierwotny grant. |
unsupported_grant_type | 400 | Obsługiwane są tylko authorization_code i refresh_token. |
slow_down | 429 | Zbyt wiele nieudanych uwierzytelnień klienta z twojego IP (30 na minutę). Odczekaj minutę. |
Wywołania API ze złym, wygasłym lub unieważnionym tokenem dostają 401; token bez potrzebnego zakresu dostaje 403 (50001 Missing Access). Odpowiedzi zawierają zwykłe nagłówki X-RateLimit-*, liczone osobno dla każdego użytkownika w każdej aplikacji.
Kiedy dostęp się kończy
Token może przestać działać przed upływem ważności. Dzieje się tak, gdy:
- użytkownik kliknie Deauthorize w Settings > Authorized Apps (wszystkie twoje tokeny tego użytkownika od razu przestają działać, a twój wpis połączonej roli dla niego zostaje usunięty)
- konto użytkownika zostanie wyłączone
- twoja aplikacja zostanie usunięta
- twoje konto dewelopera zostanie zawieszone
Traktuj 401 lub invalid_grant jako „wylogowanie” i przeprowadź użytkownika ponownie przez krok 1.
Pełny przykład (Node.js + Express)
Logowanie ze sprawdzaniem state, wymiana kodu i odczyt danych użytkownika. Wbudowany fetch wymaga Node 18+.
import express from 'express';
import crypto from 'node:crypto';
const CLIENT_ID = process.env.GAMEVOX_CLIENT_ID;
const CLIENT_SECRET = process.env.GAMEVOX_CLIENT_SECRET;
const REDIRECT_URI = 'https://yourapp.example.com/auth/gamevox';
// state must be tied to THIS browser, or someone can hand a victim a callback
// URL carrying the attacker's own code and sign them in as the attacker.
// A short-lived HttpOnly cookie does that without a session store.
const STATE_COOKIE = 'gv_oauth_state';
const readCookie = (req, name) =>
(req.headers.cookie || '').split(';').map((c) => c.trim().split('='))
.find(([k]) => k === name)?.[1] || '';
const app = express();
app.get('/login', (req, res) => {
const state = crypto.randomBytes(16).toString('hex');
res.cookie(STATE_COOKIE, state, {
httpOnly: true, secure: true, sameSite: 'lax', maxAge: 10 * 60 * 1000, path: '/auth/gamevox',
});
const url = new URL('https://gamevox.com/oauth2/authorize/');
url.search = new URLSearchParams({
response_type: 'code',
client_id: CLIENT_ID,
scope: 'identify email',
redirect_uri: REDIRECT_URI,
state,
}).toString();
res.redirect(url.toString());
});
app.get('/auth/gamevox', async (req, res) => {
const { code, state, error } = req.query;
const expected = readCookie(req, STATE_COOKIE);
res.clearCookie(STATE_COOKIE, { path: '/auth/gamevox' });
if (!expected || typeof state !== 'string' || state !== expected) {
return res.status(400).send('Login expired or started in another browser');
}
if (error) return res.redirect('/?login=cancelled');
if (typeof code !== 'string') return res.status(400).send('Missing code');
const tokenRes = await fetch('https://app-api.gamevox.com/api/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Basic ' + Buffer.from(CLIENT_ID + ':' + CLIENT_SECRET).toString('base64'),
},
body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: REDIRECT_URI }),
});
if (!tokenRes.ok) return res.status(502).send('GameVox login failed');
const tokens = await tokenRes.json();
const me = await fetch('https://app-api.gamevox.com/api/users/@me', {
headers: { Authorization: 'Bearer ' + tokens.access_token },
}).then((r) => r.json());
// me.id is the stable GameVox user id: key your account record on it.
// Store tokens.refresh_token server-side if you need access later.
res.send('Signed in as ' + me.username);
});
app.listen(3000); Różnice względem Discorda
- Tylko grant authorization code. Brak grantu implicit (
response_type=token) i brakclient_credentials. - Brak klientów publicznych: każde żądanie tokena wymaga client secret, z PKCE lub bez.
- Instalacje dla użytkownika (
applications.commandsbezbot,integration_type=1) nie są jeszcze dostępne. - Brak zakresów
connections,guilds.join,guilds.members.readirpc. - W
/users/@me/guildspolepermissionsto podsumowanie do wybierania serwera, a nie pełne wyliczenie (patrz wyżej).