OAuth2 и связанные роли¶

OAuth2 позволяет пользователю дать вашему приложению доступ к своему аккаунту: к профилю, серверам, связанным аккаунтам, праву добавить его на сервер или к связи роли для связанных ролей. bostie строит URL авторизации и вызывает API с токеном, который выдал пользователь.
import base64
import hashlib
import os
import secrets
import bostie
from bostie import (
AccessToken,
ApplicationRoleConnectionMetadata,
ApplicationRoleConnectionMetadataType,
OAuth2Scope,
oauth2,
)
CLIENT_ID = int(os.environ.get("CLIENT_ID", "0"))
CLIENT_SECRET = os.environ.get("CLIENT_SECRET", "")
REDIRECT_URI = "https://example.com/callback"
def login_url() -> tuple[str, str, str]:
state = secrets.token_urlsafe(16)
verifier = secrets.token_urlsafe(48)
digest = hashlib.sha256(verifier.encode()).digest()
challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
url = oauth2.authorize_url(
CLIENT_ID,
scopes=[
OAuth2Scope.IDENTIFY,
OAuth2Scope.GUILDS,
OAuth2Scope.ROLE_CONNECTIONS_WRITE,
],
redirect_uri=REDIRECT_URI,
state=state,
code_challenge=challenge,
)
return url, state, verifier
async def finish_login(code: str, verifier: str) -> AccessToken:
async with bostie.BearerClient() as anonymous:
return await anonymous.rest.oauth2.exchange_code(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
code=code,
redirect_uri=REDIRECT_URI,
code_verifier=verifier,
)
async def describe_user(token: AccessToken) -> str:
async with bostie.BearerClient(token.access_token) as user:
me = await user.rest.users.fetch_current()
guilds = [guild.name async for guild in user.rest.guilds.list()]
return f"{me.username} is in {len(guilds)} guilds"
async def refresh(token: AccessToken) -> AccessToken:
if token.refresh_token is None:
return token
async with bostie.BearerClient() as anonymous:
return await anonymous.rest.oauth2.refresh(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
refresh_token=token.refresh_token,
)
async def register_metadata() -> None:
async with bostie.RESTClient() as client:
await client.rest.application.update_role_connection_metadata(
CLIENT_ID,
[
ApplicationRoleConnectionMetadata(
type=ApplicationRoleConnectionMetadataType.INTEGER_GREATER_THAN_OR_EQUAL,
key="level",
name="Level",
description="The player's level in the game",
)
],
)
URL авторизации¶
oauth2.authorize_url(client_id, scopes=[...], redirect_uri=..., state=...) возвращает URL, который просит пользователя предоставить области доступа scopes и перенаправляет обратно на redirect_uri с code. Области доступа — это члены OAuth2Scope, например OAuth2Scope.IDENTIFY для identify, по одному на каждую область доступа из документации Discord; для области доступа, которую Discord добавил раньше, чем bostie дал ей имя, подходит обычная строка. Проверяйте, что перенаправление несёт отправленный вами state, чтобы другой сайт не мог заставить пользователя войти со своим кодом. Функция также принимает:
code_challenge— S256-вызов верификатора PKCE, которыйexchange_codeзатем отправляет какcode_verifier; пример его вычисляет;prompt="none", чтобы пропустить экран согласия для пользователя, который уже предоставил эти области доступа, или"consent", чтобы снова его показать;integration_type, чтобы установить приложение на сервер или для пользователя.
bostie строит только поток с кодом авторизации. Discord не рекомендует неявный поток (implicit grant), который кладёт токен в URL перенаправления и не даёт токена обновления, и советует вместо него поток с кодом и PKCE.
oauth2.invite_url(client_id, permissions=..., scopes=[...]) возвращает ссылку установки приложения с integration_type, guild_id для предварительного выбора сервера и disable_guild_select. Всё, что она не указывает, берётся из настроек установки приложения в Developer Portal.
Токены¶
BearerClient() без токена даёт доступ только к rest.oauth2:
| Метод | Что делает |
|---|---|
exchange_code(client_id=, client_secret=, code=, redirect_uri=, code_verifier=None) |
превращает код из перенаправления в AccessToken |
refresh(client_id=, client_secret=, refresh_token=) |
новый токен до истечения старого |
revoke(client_id=, client_secret=, token=) |
прекращает авторизацию токена и всех выданных им токенов |
exchange_client_credentials(client_id=, client_secret=, scopes=[...]) |
токен владельца приложения или его команды |
У AccessToken есть access_token, token_type, expires_in в секундах, scope (предоставленные области доступа через пробел) и refresh_token. Поток с кодом, включавший область доступа bot, возвращает ещё и guild, на который зашёл бот, а поток с webhook.incoming — созданный им webhook. repr токена скрывает секреты, так что запись токена в лог их не раскрывает.
Вызов API от имени пользователя¶
async with BearerClient(access_token) as user: даёт доступ к тому, что разрешают области доступа токена:
user.rest. |
Методы | Область доступа |
|---|---|---|
users |
fetch_current |
identify |
users |
list_connections |
connections |
users |
fetch_role_connection, update_role_connection, delete_role_connection |
role_connections.write |
guilds |
list |
guilds |
members |
fetch_current(guild_id) |
guilds.members.read |
commands |
fetch_permissions, list_permissions, edit_permissions |
applications.commands.permissions.update для изменения |
oauth2 |
fetch_authorization, который сообщает, что разрешает токен и кому |
любая |
lobbies |
пользовательская сторона лобби Social SDK | sdk.social_layer |
С областью доступа guilds.join бот может добавить пользователя на сервер, где состоит сам: await bot.rest.members.add(guild_id, user_id, access_token=token.access_token) возвращает нового Member или None, если пользователь уже был участником.
Связанные роли¶
Связанная роль — это роль сервера, которую пользователи получают, выполнив условия на связи с вашим приложением, например «уровень не ниже 10». Приложение один раз объявляет значения, которые сообщает, а затем сообщает значения каждого пользователя с его токеном:
- Зарегистрируйте до 5 записей метаданных через
bot.rest.application.update_role_connection_metadata(application_id, [...]), каждая —ApplicationRoleConnectionMetadataсtype,key,nameиdescription. В примере это делаетregister_metadataсRESTClient. - Укажите в Developer Portal в поле Linked Roles Verification URL приложения страницу, которая начинает поток OAuth2 с областью доступа
role_connections.write. - После обмена кода вызовите
user.rest.users.update_role_connection(application_id, platform_name=..., platform_username=..., metadata={"level": "12"}). Значения метаданных — строки, как их принимает API Discord.
Пример связанных ролей — это готовая страница проверки. fmt.guild_navigation("linked-roles", role_id) даёт в чате ссылку на связанную роль сервера.