summaryrefslogtreecommitdiff
path: root/docs/handbook/meshmc/account-management.md
blob: c38eef014dbeef6f530bef8fa4881a07f55b4e11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# Account Management

## Overview

MeshMC supports Microsoft Account (MSA) authentication for Minecraft login. The authentication system implements the full MSA → Xbox Live → Minecraft authentication chain using Qt6's `QOAuth2AuthorizationCodeFlow` and the Katabasis OAuth2 library. Multiple accounts can be stored, managed, and switched between.

## Authentication Architecture

### Key Classes

| Class | File | Purpose |
|---|---|---|
| `MinecraftAccount` | `minecraft/auth/MinecraftAccount.{h,cpp}` | Account model |
| `AccountList` | `minecraft/auth/AccountList.{h,cpp}` | Account collection model |
| `AccountData` | `minecraft/auth/AccountData.{h,cpp}` | Token/profile data storage |
| `AccountTask` | `minecraft/auth/AccountTask.{h,cpp}` | Async auth task base |
| `AuthFlow` | `minecraft/auth/flows/AuthFlow.{h,cpp}` | Auth step pipeline |
| `MSAInteractive` | `minecraft/auth/flows/MSA.{h,cpp}` | Interactive MSA login |
| `MSASilent` | `minecraft/auth/flows/MSA.{h,cpp}` | Silent token refresh |
| `AuthStep` | `minecraft/auth/AuthStep.{h,cpp}` | Abstract auth pipeline step |
| `AuthSession` | `minecraft/auth/AuthSession.{h,cpp}` | Resolved session for launch |
| `AuthRequest` | `minecraft/auth/AuthRequest.{h,cpp}` | HTTP request helper |
| `Parsers` | `minecraft/auth/Parsers.{h,cpp}` | Response JSON parsers |
| `MSALoginDialog` | `ui/dialogs/MSALoginDialog.{h,cpp}` | Login UI dialog |

### Authentication Steps

| Step | File | Purpose |
|---|---|---|
| `MSAStep` | `steps/MSAStep.{h,cpp}` | OAuth2 browser-based login |
| `XboxUserStep` | `steps/XboxUserStep.{h,cpp}` | Exchange MSA token → Xbox User Token |
| `XboxAuthorizationStep` | `steps/XboxAuthorizationStep.{h,cpp}` | Exchange → XSTS Token |
| `XboxProfileStep` | `steps/XboxProfileStep.{h,cpp}` | Fetch Xbox profile |
| `MinecraftProfileStep` | `steps/MinecraftProfileStep.{h,cpp}` | Fetch Minecraft profile |
| `EntitlementsStep` | `steps/EntitlementsStep.{h,cpp}` | Verify game ownership |
| `GetSkinStep` | `steps/GetSkinStep.{h,cpp}` | Download player skin |
| `MeshMCLoginStep` | `steps/MeshMCLoginStep.{h,cpp}` | MeshMC-specific login |

## MSA Authentication Flow

### Interactive Login (First-Time)

The `MSAInteractive` flow is used when the user needs to sign in for the first time:

```
MSAStep(Login)
    │
    ├── QOAuth2AuthorizationCodeFlow configured with:
    │   ├── authorizationUrl: https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize
    │   ├── accessTokenUrl: https://login.microsoftonline.com/consumers/oauth2/v2.0/token
    │   ├── clientId: MeshMC_MICROSOFT_CLIENT_ID (from BuildConfig)
    │   └── scope: XboxLive.signin XboxLive.offline_access
    │
    ├── Opens browser for user authentication
    ├── QOAuthHttpServerReplyHandler listens on localhost
    ├── Receives authorization code callback
    └── Exchanges code for MSA tokens
    │
    ▼
XboxUserStep
    │
    ├── POST https://user.auth.xboxlive.com/user/authenticate
    ├── Body: { "Properties": { "AuthMethod": "RPS", "SiteName": "user.auth.xboxlive.com",
    │              "RpsTicket": "d=<MSA_ACCESS_TOKEN>" }, "RelyingParty": "http://auth.xboxlive.com",
    │              "TokenType": "JWT" }
    └── Returns: Xbox User Token + user hash
    │
    ▼
XboxAuthorizationStep
    │
    ├── POST https://xsts.auth.xboxlive.com/xsts/authorize
    ├── Body: { "Properties": { "SandboxId": "RETAIL",
    │              "UserTokens": ["<XBOX_USER_TOKEN>"] },
    │              "RelyingParty": "rp://api.minecraftservices.com/",
    │              "TokenType": "JWT" }
    └── Returns: XSTS Token + user hash
    │
    ▼
MinecraftProfileStep
    │
    ├── POST https://api.minecraftservices.com/authentication/login_with_xbox
    ├── Body: { "identityToken": "XBL3.0 x=<USERHASH>;<XSTS_TOKEN>" }
    ├── Returns: Minecraft access token
    │
    ├── GET https://api.minecraftservices.com/minecraft/profile
    └── Returns: player UUID, name, skin, capes
    │
    ▼
EntitlementsStep
    │
    ├── GET https://api.minecraftservices.com/entitlements/mcstore
    └── Verifies: ownsMinecraft, canPlayMinecraft
    │
    ▼
GetSkinStep
    │
    ├── Downloads skin texture from profile URL
    └── Stores skin data in AccountData
```

### Silent Refresh

The `MSASilent` flow is used for automatic token refresh after the initial login:

```
MSAStep(Refresh)
    │
    ├── Uses stored refresh token
    ├── QOAuth2AuthorizationCodeFlow::refreshAccessToken()
    └── Exchanges refresh token for new MSA tokens
    │
    ▼
(Same steps as Interactive: XboxUser → Xbox Authorization → MinecraftProfile → etc.)
```

## MSAStep Implementation

`MSAStep` uses Qt6's `QOAuth2AuthorizationCodeFlow`:

```cpp
class MSAStep : public AuthStep
{
    Q_OBJECT
public:
    enum Action { Refresh, Login };

    explicit MSAStep(AccountData* data, Action action);
    virtual ~MSAStep() noexcept;

    void perform() override;
    void rehydrate() override;
    QString describe() override;

private slots:
    void onGranted();
    void onRequestFailed(QAbstractOAuth::Error error);
    void onOpenBrowser(const QUrl& url);
};
```

For `Login` action:
- Creates `QOAuth2AuthorizationCodeFlow`
- Starts a `QOAuthHttpServerReplyHandler` on a random local port
- Opens the user's browser to Microsoft's login page
- Receives the callback and exchanges the code for tokens

For `Refresh` action:
- Uses the stored refresh token
- Calls `refreshAccessToken()` on the flow
- No browser interaction needed

## AuthFlow Pipeline

`AuthFlow` orchestrates the step sequence:

```cpp
class AuthFlow : public AccountTask
{
    Q_OBJECT
public:
    explicit AuthFlow(AccountData* data, QObject* parent = 0);

    void executeTask() override;

private slots:
    void stepFinished(AccountTaskState resultingState, QString message);

protected:
    void succeed();
    void nextStep();

    QList<AuthStep::Ptr> m_steps;
    AuthStep::Ptr m_currentStep;
};
```

Steps are executed sequentially. Each step calls `stepFinished()` when complete, which either advances to the next step or handles errors.

## AccountData

`AccountData` stores all authentication state for a single account:

```cpp
struct AccountData {
    // Serialization
    QJsonObject saveState() const;
    bool resumeStateFromV3(QJsonObject data);

    // Display
    QString accountDisplayString() const;   // Gamertag
    QString accessToken() const;             // Minecraft access token
    QString profileId() const;               // Minecraft UUID
    QString profileName() const;             // Minecraft username
    QString lastError() const;

    // Account type
    AccountType type = AccountType::MSA;

    // Token storage
    Katabasis::Token msaToken;              // Microsoft Account token
    Katabasis::Token userToken;             // Xbox User Token
    Katabasis::Token xboxApiToken;          // XSTS Token
    Katabasis::Token mojangservicesToken;   // Mojang services token
    Katabasis::Token yggdrasilToken;        // Minecraft access token

    // Profile data
    MinecraftProfile minecraftProfile;      // UUID, name, skin, capes
    MinecraftEntitlement minecraftEntitlement;  // Game ownership

    // Internal
    Katabasis::Validity validity_;
    QString internalId;                     // Runtime-only unique ID
};
```

### Token Stack

Each token is stored as a `Katabasis::Token`:
- `token` — the actual token string
- `refresh_token` — token for refresh operations
- `validity` — `Katabasis::Validity::None`, `Assumed`, or `Certain`
- `extra` — additional token metadata

### MinecraftProfile

```cpp
struct MinecraftProfile {
    QString id;                   // UUID
    QString name;                 // Player name
    Skin skin;                    // Active skin
    QString currentCape;          // Active cape ID
    QMap<QString, Cape> capes;    // Available capes
    Katabasis::Validity validity;
};

struct Skin {
    QString id;
    QString url;
    QString variant;    // "classic" or "slim"
    QByteArray data;    // Texture data
};
```

## MinecraftAccount

`MinecraftAccount` is the QObject wrapper around `AccountData`:

```cpp
class MinecraftAccount : public QObject, public Usable
{
    Q_OBJECT
public:
    static MinecraftAccountPtr createBlankMSA();
    static MinecraftAccountPtr loadFromJsonV3(const QJsonObject& json);
    QJsonObject saveToJson() const;

    // Auth operations
    shared_qobject_ptr<AccountTask> loginMSA();
    shared_qobject_ptr<AccountTask> refresh();
    shared_qobject_ptr<AccountTask> currentTask();

    // Queries
    QString internalId() const;
    QString accountDisplayString() const;
    QString accessToken() const;
    QString profileId() const;
    QString profileName() const;
    bool isActive() const;

signals:
    void changed();
    void activityChanged(bool active);

public:
    AccountData data;
};
```

### Account States

```cpp
enum class AccountState {
    Unchecked,   // Not yet validated
    Offline,     // Tokens exist but not verified
    Working,     // Auth operation in progress
    Online,      // Valid tokens, verified
    Errored,     // Auth operation failed
    Expired,     // Tokens expired
    Gone         // Account removed from Microsoft
};
```

## AccountList

`AccountList` manages the collection of Microsoft accounts:

```cpp
class AccountList : public QAbstractListModel
{
    Q_OBJECT
public:
    enum VListColumns {
        NameColumn = 0,
        ProfileNameColumn,
        TypeColumn,
        StatusColumn,
        NUM_COLUMNS
    };

    const MinecraftAccountPtr at(int i) const;
    int count() const;

    void addAccount(const MinecraftAccountPtr account);
    void removeAccount(QModelIndex index);
    int findAccountByProfileId(const QString& profileId) const;
    MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const;
    QStringList profileNames() const;

    void requestRefresh(QString accountId);
    void queueRefresh(QString accountId);

    void setListFilePath(QString path, bool autosave = false);
    bool loadList();
    bool saveList();

    MinecraftAccountPtr defaultAccount() const;
    void setDefaultAccount(MinecraftAccountPtr profileId);
    bool anyAccountIsValid();
};
```

### Persistence (`accounts.json`)

Accounts are serialized to `accounts.json` in the data directory:

```json
{
    "formatVersion": 3,
    "accounts": [
        {
            "type": "MSA",
            "msa": { ... token data ... },
            "utoken": { ... xbox user token ... },
            "xrp-main": { ... xsts token ... },
            "ygg": { ... minecraft access token ... },
            "profile": {
                "id": "player-uuid",
                "name": "PlayerName",
                "skin": { ... }
            },
            "entitlement": {
                "ownsMinecraft": true,
                "canPlayMinecraft": true
            }
        }
    ],
    "activeAccount": "player-uuid"
}
```

### Token Refresh Queue

`AccountList` maintains a refresh queue:
- `requestRefresh()` pushes an account to the front (high priority)
- `queueRefresh()` adds to the back (low priority)
- Refresh operations run sequentially to avoid rate limiting

## AuthSession

`AuthSession` is the resolved auth session passed to the launch system:

```cpp
struct AuthSession {
    QString player_name;        // Minecraft username
    QString uuid;               // Player UUID
    QString access_token;       // Minecraft access token
    QString session;            // Legacy session token (deprecated)
    QString user_type;          // "msa"

    // Used for censor filter
    QMap<QString, QString> sensitiveStrings();
};
```

## MSALoginDialog

The login dialog (`ui/dialogs/MSALoginDialog.h`) provides the user interface for Microsoft authentication:

```cpp
class MSALoginDialog : public QDialog {
    Q_OBJECT
};
```

The dialog:
1. Starts the `MSAInteractive` auth flow
2. Displays a message asking the user to sign in via their browser
3. Shows the authentication URL and a "Copy to Clipboard" button
4. Shows progress as auth steps complete
5. Closes on success or shows error on failure

## Account Management UI

### AccountListPage (`ui/pages/global/AccountListPage.h`)

The global settings page for account management:
- Lists all stored Microsoft accounts
- Shows account status (Online, Offline, Expired, etc.)
- "Add Microsoft Account" button → opens `MSALoginDialog`
- "Remove" button → deletes selected account
- "Set Default" button → sets the default account for launches
- "Refresh" button → triggers token refresh for selected account
- Displays skin preview for the selected account

### ProfileSelectDialog (`ui/dialogs/ProfileSelectDialog.h`)

Prompt dialog shown when launching without a default account:
- Lists available accounts
- User selects which account to use
- Returns the selected `MinecraftAccountPtr`

### ProfileSetupDialog (`ui/dialogs/ProfileSetupDialog.h`)

Dialog for initial profile setup when a Microsoft account doesn't yet have a Minecraft profile:
- Lets the user set their Minecraft username
- Submits the profile creation request to Mojang's API

## Skin Management

### SkinUploadDialog (`ui/dialogs/SkinUploadDialog.h`)

Upload dialog for changing the player's skin:
- Select a skin file (PNG)
- Choose variant (classic or slim)
- Upload to Mojang's API using the access token

### SkinUtils (`launcher/SkinUtils.h`)

Utility functions for skin image processing:
- Download skin textures
- Parse skin images for display

## Security Considerations

### Token Storage

Tokens are stored in `accounts.json` in the user's data directory:
- File is readable only by the user (standard OS file permissions)
- Tokens include MSA refresh tokens, Xbox tokens, and Minecraft access tokens
- **No encryption at rest** — the file relies on OS-level file permissions

### Censor Filter

The launch system automatically censors sensitive tokens from game log output:

```cpp
QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session);
```

This replaces token strings in log output with placeholder text to prevent accidental exposure.

### Client ID

The Microsoft OAuth2 client ID is embedded at build time:

```cmake
set(MeshMC_MICROSOFT_CLIENT_ID "3035382c-8f73-493a-b579-d182905c2864")
```

This is a public client ID registered with Azure Active Directory for the MeshMC application.