baltica 0.1.19 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -16
- package/dist/client/client.d.ts +3 -0
- package/dist/client/client.js +16 -6
- package/dist/client/types/client-events.d.ts +1 -0
- package/dist/client/worker/worker-client.js +1 -1
- package/dist/shared/auth/authentication.d.ts +25 -5
- package/dist/shared/auth/authentication.js +369 -49
- package/dist/shared/auth.js +2 -0
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -51,29 +51,56 @@ const client = new Client({
|
|
|
51
51
|
port: 19132,
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
// Connect and get server info
|
|
55
54
|
await client.connect();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Email/Password Authentication
|
|
58
|
+
|
|
59
|
+
You can authenticate directly with your Microsoft account using email and password:
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
client
|
|
59
|
-
|
|
61
|
+
```typescript
|
|
62
|
+
const client = new Client({
|
|
63
|
+
address: "play.server.com",
|
|
64
|
+
port: 19132,
|
|
65
|
+
email: "your-email@outlook.com",
|
|
66
|
+
password: "your-password",
|
|
60
67
|
});
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
await client.connect();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Important Notes:**
|
|
73
|
+
- This method only works with accounts that have 2FA (Two-Factor Authentication) **disabled**
|
|
74
|
+
- The account must have an Xbox profile and own Minecraft Bedrock Edition or launched minecraft at least once.
|
|
75
|
+
- User tokens are cached for ~14 days to minimize login requests (BETA)
|
|
76
|
+
- Tokens are stored in the `tokens` folder by default
|
|
77
|
+
|
|
78
|
+
### Using a Proxy
|
|
79
|
+
|
|
80
|
+
Baltica supports SOCKS5 proxies for client connections:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const client = new Client({
|
|
84
|
+
address: "play.server.com",
|
|
85
|
+
port: 19132,
|
|
86
|
+
email: "your-email@outlook.com",
|
|
87
|
+
password: "your-password",
|
|
88
|
+
proxy: {
|
|
89
|
+
host: "proxy.example.com",
|
|
90
|
+
port: 1080,
|
|
91
|
+
userId: "proxy-username", // Optional
|
|
92
|
+
password: "proxy-password", // Optional
|
|
93
|
+
},
|
|
74
94
|
});
|
|
95
|
+
|
|
96
|
+
await client.connect();
|
|
75
97
|
```
|
|
76
98
|
|
|
99
|
+
This is useful for:
|
|
100
|
+
- Bypassing IP restrictions
|
|
101
|
+
- Testing from different geographic locations
|
|
102
|
+
- Managing multiple bot connections
|
|
103
|
+
|
|
77
104
|
### Server Usage
|
|
78
105
|
|
|
79
106
|
Want to create your own Minecraft server? We got you:
|
package/dist/client/client.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export declare class Client extends Emitter<ClientEvents> {
|
|
|
43
43
|
startGameData: StartGamePacket;
|
|
44
44
|
/** Whether we should continue after sending Login (Proxy use) */
|
|
45
45
|
cancelPastLogin: boolean;
|
|
46
|
+
private disconnectReason?;
|
|
46
47
|
constructor(options: Partial<ClientOptions>);
|
|
47
48
|
/** Connect to the server and start sending/receiving packets. */
|
|
48
49
|
connect(): Promise<[StartGamePacket]>;
|
|
@@ -53,6 +54,8 @@ export declare class Client extends Emitter<ClientEvents> {
|
|
|
53
54
|
processPacket(buffer: Buffer): void;
|
|
54
55
|
/** Do not call this, leaving it public incase someone needs to override this for some reason. */
|
|
55
56
|
handleGamePackets(): void;
|
|
57
|
+
disconnect(reason?: string): void;
|
|
58
|
+
private cleanup;
|
|
56
59
|
startEncryption(iv: Buffer): void;
|
|
57
60
|
private waitForSessionReady;
|
|
58
61
|
/**
|
package/dist/client/client.js
CHANGED
|
@@ -44,9 +44,7 @@ class Client extends shared_1.Emitter {
|
|
|
44
44
|
});
|
|
45
45
|
/** Create ClientData to store and handle auth data */
|
|
46
46
|
this.data = new types_2.ClientData(this);
|
|
47
|
-
|
|
48
|
-
/** Session event gets mojang (minecraft) auth session */
|
|
49
|
-
/** NOTE! This takes like 30-100ms for offline mode which kinda feels slow but not really */
|
|
47
|
+
this.raknet.on("disconnect", () => this.cleanup());
|
|
50
48
|
this.once("session", () => {
|
|
51
49
|
this.sessionReady = true;
|
|
52
50
|
});
|
|
@@ -184,11 +182,23 @@ class Client extends shared_1.Emitter {
|
|
|
184
182
|
}
|
|
185
183
|
});
|
|
186
184
|
this.on("DisconnectPacket", (packet) => {
|
|
187
|
-
this.
|
|
188
|
-
this.raknet.disconnect();
|
|
189
|
-
console.log(packet.message);
|
|
185
|
+
this.disconnect(packet.message.message ?? undefined);
|
|
190
186
|
});
|
|
191
187
|
}
|
|
188
|
+
disconnect(reason) {
|
|
189
|
+
if (this.status === raknet_1.ConnectionStatus.Disconnected)
|
|
190
|
+
return;
|
|
191
|
+
this.disconnectReason = reason;
|
|
192
|
+
this.raknet.disconnect();
|
|
193
|
+
}
|
|
194
|
+
cleanup() {
|
|
195
|
+
this.status = raknet_1.ConnectionStatus.Disconnected;
|
|
196
|
+
this._encryptionEnabled = false;
|
|
197
|
+
this._compressionEnabled = false;
|
|
198
|
+
this.sessionReady = false;
|
|
199
|
+
this.emit("disconnect", this.disconnectReason);
|
|
200
|
+
this.disconnectReason = undefined;
|
|
201
|
+
}
|
|
192
202
|
startEncryption(iv) {
|
|
193
203
|
this.packetEncryptor = new shared_1.PacketEncryptor(this, iv);
|
|
194
204
|
this._encryptionEnabled = true;
|
|
@@ -6,7 +6,7 @@ const worker_1 = require("./worker");
|
|
|
6
6
|
class WorkerClient extends raknet_1.EventEmitter {
|
|
7
7
|
constructor(options) {
|
|
8
8
|
super();
|
|
9
|
-
this._options = { ...raknet_1.
|
|
9
|
+
this._options = { ...(0, raknet_1.createDefaultClientOptions)(), ...options };
|
|
10
10
|
this._worker = (0, worker_1.connect)(this._options);
|
|
11
11
|
this.handleEvents();
|
|
12
12
|
}
|
|
@@ -1,20 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Xbox Live Authentication Module
|
|
3
|
+
*
|
|
4
|
+
* Based on the authentication flow from @xboxreplay/xboxlive-auth
|
|
5
|
+
* https://github.com/XboxReplay/xboxlive-auth
|
|
6
|
+
*
|
|
7
|
+
* Modified to support SOCKS5 proxies for all HTTP requests and Minecraft Authentication
|
|
8
|
+
*/
|
|
3
9
|
export interface BedrockTokens {
|
|
4
10
|
chains: string[];
|
|
5
11
|
xuid: string;
|
|
6
12
|
gamertag: string;
|
|
7
13
|
userHash: string;
|
|
8
14
|
}
|
|
15
|
+
export interface ProxyOptions {
|
|
16
|
+
host: string;
|
|
17
|
+
port: number;
|
|
18
|
+
userId?: string;
|
|
19
|
+
password?: string;
|
|
20
|
+
}
|
|
9
21
|
export interface AuthOptions {
|
|
10
22
|
email: string;
|
|
11
23
|
password: string;
|
|
12
24
|
clientPublicKey: string;
|
|
25
|
+
cacheDir?: string;
|
|
26
|
+
proxy?: ProxyOptions;
|
|
13
27
|
}
|
|
14
28
|
/**
|
|
15
29
|
* Authenticates with Xbox Live using email/password and obtains Minecraft Bedrock tokens
|
|
16
|
-
*
|
|
30
|
+
* Supports SOCKS5 proxy for all authentication requests
|
|
17
31
|
*/
|
|
18
32
|
export declare function authenticateWithCredentials(options: AuthOptions): Promise<BedrockTokens>;
|
|
19
|
-
export
|
|
20
|
-
export
|
|
33
|
+
export type Email = string;
|
|
34
|
+
export interface AuthenticateResponse {
|
|
35
|
+
access_token: string;
|
|
36
|
+
token_type: string;
|
|
37
|
+
expires_in: number;
|
|
38
|
+
scope: string;
|
|
39
|
+
user_id: string;
|
|
40
|
+
}
|
|
@@ -1,67 +1,383 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Xbox Live Authentication Module
|
|
4
|
+
*
|
|
5
|
+
* Based on the authentication flow from @xboxreplay/xboxlive-auth
|
|
6
|
+
* https://github.com/XboxReplay/xboxlive-auth
|
|
7
|
+
*
|
|
8
|
+
* Modified to support SOCKS5 proxies for all HTTP requests and Minecraft Authentication
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
2
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.xnet = exports.live = exports.authenticate = void 0;
|
|
4
44
|
exports.authenticateWithCredentials = authenticateWithCredentials;
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
Object.defineProperty(exports, "live", { enumerable: true, get: function () { return xboxlive_auth_1.live; } });
|
|
8
|
-
Object.defineProperty(exports, "xnet", { enumerable: true, get: function () { return xboxlive_auth_1.xnet; } });
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
9
47
|
const raknet_1 = require("@sanctumterra/raknet");
|
|
48
|
+
const fetch_socks_1 = require("fetch-socks");
|
|
49
|
+
const undici_1 = require("undici");
|
|
10
50
|
const MINECRAFT_BEDROCK_RELYING_PARTY = "https://multiplayer.minecraft.net/";
|
|
51
|
+
const XBOX_AUTH_CLIENT_ID = "00000000441cc96b";
|
|
52
|
+
function hashString(str) {
|
|
53
|
+
let hash = 0;
|
|
54
|
+
for (let i = 0; i < str.length; i++) {
|
|
55
|
+
const char = str.charCodeAt(i);
|
|
56
|
+
hash = (hash << 5) - hash + char;
|
|
57
|
+
hash = hash & hash;
|
|
58
|
+
}
|
|
59
|
+
return Math.abs(hash).toString(16).slice(0, 6);
|
|
60
|
+
}
|
|
61
|
+
function ensureDir(dir) {
|
|
62
|
+
if (!fs.existsSync(dir)) {
|
|
63
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function getCacheFile(cacheDir, email) {
|
|
67
|
+
return path.join(cacheDir, `${hashString(email)}_xbl-user-cache.json`);
|
|
68
|
+
}
|
|
69
|
+
function loadCache(cacheFile) {
|
|
70
|
+
try {
|
|
71
|
+
if (fs.existsSync(cacheFile)) {
|
|
72
|
+
const cached = JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
|
|
73
|
+
const expiresAt = new Date(cached.notAfter).getTime();
|
|
74
|
+
if (Date.now() < expiresAt - 3600000) {
|
|
75
|
+
return cached;
|
|
76
|
+
}
|
|
77
|
+
raknet_1.Logger.info("Cached user token expired");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* ignore */
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function saveCache(cacheFile, userToken, userHash, notAfter) {
|
|
86
|
+
try {
|
|
87
|
+
ensureDir(path.dirname(cacheFile));
|
|
88
|
+
const data = {
|
|
89
|
+
userToken,
|
|
90
|
+
userHash,
|
|
91
|
+
notAfter,
|
|
92
|
+
obtainedOn: Date.now(),
|
|
93
|
+
};
|
|
94
|
+
fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* ignore */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function createProxiedFetch(proxy) {
|
|
101
|
+
if (!proxy) {
|
|
102
|
+
return fetch;
|
|
103
|
+
}
|
|
104
|
+
const dispatcher = (0, fetch_socks_1.socksDispatcher)({
|
|
105
|
+
type: 5,
|
|
106
|
+
host: proxy.host,
|
|
107
|
+
port: proxy.port,
|
|
108
|
+
userId: proxy.userId,
|
|
109
|
+
password: proxy.password,
|
|
110
|
+
});
|
|
111
|
+
return async (input, init) => {
|
|
112
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
113
|
+
const response = await (0, undici_1.fetch)(url, {
|
|
114
|
+
...init,
|
|
115
|
+
dispatcher,
|
|
116
|
+
});
|
|
117
|
+
return response;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
11
120
|
/**
|
|
12
121
|
* Authenticates with Xbox Live using email/password and obtains Minecraft Bedrock tokens
|
|
13
|
-
*
|
|
122
|
+
* Supports SOCKS5 proxy for all authentication requests
|
|
14
123
|
*/
|
|
15
124
|
async function authenticateWithCredentials(options) {
|
|
16
|
-
const { email, password, clientPublicKey } = options;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
125
|
+
const { email, password, clientPublicKey, cacheDir, proxy } = options;
|
|
126
|
+
const cacheFile = cacheDir ? getCacheFile(cacheDir, email) : null;
|
|
127
|
+
const proxiedFetch = createProxiedFetch(proxy);
|
|
128
|
+
// Verify proxy is working by checking our IP
|
|
129
|
+
if (proxy) {
|
|
130
|
+
try {
|
|
131
|
+
const ipResp = await proxiedFetch("https://api.ipify.org?format=json");
|
|
132
|
+
const ipData = (await ipResp.json());
|
|
133
|
+
raknet_1.Logger.info(`Proxy IP verified: ${ipData.ip}`);
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
raknet_1.Logger.warn(`Could not verify proxy IP: ${e instanceof Error ? e.message : String(e)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
let userToken;
|
|
140
|
+
let userHash;
|
|
141
|
+
const cached = cacheFile ? loadCache(cacheFile) : null;
|
|
142
|
+
if (cached) {
|
|
143
|
+
raknet_1.Logger.info("Using cached Xbox user token...");
|
|
144
|
+
userToken = cached.userToken;
|
|
145
|
+
userHash = cached.userHash;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
raknet_1.Logger.info(`Authenticating with Xbox Live...${proxy ? ` (via proxy ${proxy.host}:${proxy.port})` : ""}`);
|
|
149
|
+
const accessToken = await getMicrosoftAccessToken(email, password, proxiedFetch);
|
|
150
|
+
const userTokenResp = await exchangeRpsTicketForUserToken(accessToken, proxiedFetch);
|
|
151
|
+
userToken = userTokenResp.Token;
|
|
152
|
+
userHash = userTokenResp.DisplayClaims.xui[0].uhs;
|
|
153
|
+
if (cacheFile) {
|
|
154
|
+
saveCache(cacheFile, userToken, userHash, userTokenResp.NotAfter);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const xstsResp = await exchangeTokenForXSTSToken(userToken, MINECRAFT_BEDROCK_RELYING_PARTY, proxiedFetch);
|
|
158
|
+
const xuid = xstsResp.DisplayClaims.xui[0].xid || "";
|
|
159
|
+
const chains = await getMinecraftBedrockChains(xstsResp.Token, userHash, clientPublicKey, proxiedFetch);
|
|
160
|
+
const gamertag = extractGamertagFromChains(chains);
|
|
161
|
+
raknet_1.Logger.info(`Authenticated as: ${gamertag} (${xuid})`);
|
|
162
|
+
return { chains, xuid, gamertag, userHash };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get Microsoft access token using email/password via OAuth flow
|
|
166
|
+
* This implements the full browser-like login flow
|
|
167
|
+
*/
|
|
168
|
+
async function getMicrosoftAccessToken(email, password, proxiedFetch) {
|
|
169
|
+
const authUrl = "https://login.live.com/oauth20_authorize.srf";
|
|
170
|
+
const params = new URLSearchParams({
|
|
171
|
+
client_id: XBOX_AUTH_CLIENT_ID,
|
|
172
|
+
redirect_uri: "https://login.live.com/oauth20_desktop.srf",
|
|
173
|
+
response_type: "token",
|
|
174
|
+
scope: "service::user.auth.xboxlive.com::MBI_SSL",
|
|
175
|
+
display: "touch",
|
|
176
|
+
locale: "en",
|
|
177
|
+
});
|
|
178
|
+
const preAuthResp = await proxiedFetch(`${authUrl}?${params}`, {
|
|
179
|
+
headers: {
|
|
180
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
181
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
182
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
if (!preAuthResp.ok) {
|
|
186
|
+
throw new Error(`Pre-auth request failed: ${preAuthResp.status}`);
|
|
187
|
+
}
|
|
188
|
+
const preAuthHtml = await preAuthResp.text();
|
|
189
|
+
const cookies = extractCookies(preAuthResp.headers);
|
|
190
|
+
const { ppft, urlPost } = extractLoginParams(preAuthHtml);
|
|
191
|
+
const loginBody = new URLSearchParams({
|
|
192
|
+
login: email,
|
|
193
|
+
loginfmt: email,
|
|
194
|
+
passwd: password,
|
|
195
|
+
PPFT: ppft,
|
|
196
|
+
PPSX: "Passpor",
|
|
197
|
+
NewUser: "1",
|
|
198
|
+
FoundMSAs: "",
|
|
199
|
+
fspost: "0",
|
|
200
|
+
i21: "0",
|
|
201
|
+
CookieDisclosure: "0",
|
|
202
|
+
IsFidoSupported: "1",
|
|
203
|
+
isSignupPost: "0",
|
|
204
|
+
isRecoveryAttemptPost: "0",
|
|
205
|
+
i13: "0",
|
|
206
|
+
i19: Math.floor(Math.random() * 100000).toString(),
|
|
207
|
+
});
|
|
208
|
+
const loginResp = await proxiedFetch(urlPost, {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: {
|
|
211
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
212
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
213
|
+
Cookie: cookies,
|
|
214
|
+
Referer: `${authUrl}?${params}`,
|
|
215
|
+
Origin: "https://login.live.com",
|
|
216
|
+
},
|
|
217
|
+
body: loginBody.toString(),
|
|
218
|
+
redirect: "manual",
|
|
219
|
+
});
|
|
220
|
+
// Check for access token in redirect
|
|
221
|
+
let location = loginResp.headers.get("location") || "";
|
|
222
|
+
// Follow redirects manually to find the access token
|
|
223
|
+
let attempts = 0;
|
|
224
|
+
while (attempts < 5 && !location.includes("access_token=")) {
|
|
225
|
+
if (!location) {
|
|
226
|
+
// Check if we got an error page
|
|
227
|
+
const responseText = await loginResp.text();
|
|
228
|
+
if (responseText.includes("sErrTxt") ||
|
|
229
|
+
responseText.includes("Your account or password is incorrect")) {
|
|
230
|
+
throw new Error("Invalid credentials");
|
|
231
|
+
}
|
|
232
|
+
if (responseText.includes("Sign in a different way") ||
|
|
233
|
+
responseText.includes("idA_PWD_SwitchToCredPicker")) {
|
|
234
|
+
throw new Error("2FA is enabled on this account. Direct login requires 2FA to be disabled.");
|
|
235
|
+
}
|
|
236
|
+
// Try to extract access token from response body (some flows embed it)
|
|
237
|
+
const tokenMatch = responseText.match(/access_token=([^&"']+)/);
|
|
238
|
+
if (tokenMatch) {
|
|
239
|
+
return decodeURIComponent(tokenMatch[1]);
|
|
240
|
+
}
|
|
241
|
+
throw new Error("Failed to get redirect URL from login response");
|
|
242
|
+
}
|
|
243
|
+
if (location.includes("access_token=")) {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
const redirectResp = await proxiedFetch(location, {
|
|
247
|
+
headers: {
|
|
248
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
249
|
+
Cookie: cookies,
|
|
250
|
+
},
|
|
251
|
+
redirect: "manual",
|
|
30
252
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
253
|
+
location = redirectResp.headers.get("location") || "";
|
|
254
|
+
attempts++;
|
|
255
|
+
}
|
|
256
|
+
// Extract access token from URL fragment
|
|
257
|
+
const tokenMatch = location.match(/access_token=([^&]+)/);
|
|
258
|
+
if (tokenMatch) {
|
|
259
|
+
return decodeURIComponent(tokenMatch[1]);
|
|
260
|
+
}
|
|
261
|
+
throw new Error("Failed to obtain access token from Microsoft");
|
|
262
|
+
}
|
|
263
|
+
function extractCookies(headers) {
|
|
264
|
+
const setCookies = headers.get("set-cookie");
|
|
265
|
+
if (!setCookies)
|
|
266
|
+
return "";
|
|
267
|
+
// Parse and combine cookies
|
|
268
|
+
const cookies = [];
|
|
269
|
+
const cookieStrings = setCookies.split(/,(?=[^;]*=)/);
|
|
270
|
+
for (const cookieStr of cookieStrings) {
|
|
271
|
+
const match = cookieStr.match(/^([^=]+)=([^;]*)/);
|
|
272
|
+
if (match) {
|
|
273
|
+
cookies.push(`${match[1].trim()}=${match[2]}`);
|
|
43
274
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
275
|
+
}
|
|
276
|
+
return cookies.join("; ");
|
|
277
|
+
}
|
|
278
|
+
function extractLoginParams(html) {
|
|
279
|
+
let ppft = null;
|
|
280
|
+
// Pattern for sFTTag with escaped quotes (JSON format)
|
|
281
|
+
const sFTTagMatch = html.match(/sFTTag":"<input[^>]*value=\\"([^"\\]+)\\"/);
|
|
282
|
+
if (sFTTagMatch) {
|
|
283
|
+
ppft = sFTTagMatch[1];
|
|
284
|
+
}
|
|
285
|
+
// Fallback patterns
|
|
286
|
+
if (!ppft) {
|
|
287
|
+
const ppftPatterns = [
|
|
288
|
+
/sFTTag:'[^']*value="([^"]+)"/,
|
|
289
|
+
/name="PPFT"[^>]*value="([^"]+)"/,
|
|
290
|
+
/value="([^"]+)"[^>]*name="PPFT"/,
|
|
291
|
+
/<input[^>]*name="PPFT"[^>]*value="([^"]+)"/,
|
|
292
|
+
/"sFT"\s*:\s*"([^"]+)"/,
|
|
293
|
+
/sFT:'([^']+)'/,
|
|
294
|
+
/"sFT":"([^"]+)"/,
|
|
295
|
+
];
|
|
296
|
+
for (const pattern of ppftPatterns) {
|
|
297
|
+
const match = html.match(pattern);
|
|
298
|
+
if (match) {
|
|
299
|
+
ppft = match[1];
|
|
300
|
+
break;
|
|
58
301
|
}
|
|
59
302
|
}
|
|
60
|
-
throw error;
|
|
61
303
|
}
|
|
304
|
+
if (!ppft) {
|
|
305
|
+
throw new Error("Failed to extract PPFT token from login page");
|
|
306
|
+
}
|
|
307
|
+
// Extract urlPost
|
|
308
|
+
const urlPostPatterns = [
|
|
309
|
+
/urlPost:\s*'([^']+)'/,
|
|
310
|
+
/urlPost:\s*"([^"]+)"/,
|
|
311
|
+
/"urlPost"\s*:\s*"([^"]+)"/,
|
|
312
|
+
];
|
|
313
|
+
let urlPost = "https://login.live.com/ppsecure/post.srf";
|
|
314
|
+
for (const pattern of urlPostPatterns) {
|
|
315
|
+
const match = html.match(pattern);
|
|
316
|
+
if (match) {
|
|
317
|
+
urlPost = match[1];
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { ppft, urlPost };
|
|
62
322
|
}
|
|
63
|
-
|
|
64
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Exchange Microsoft access token for Xbox User Token
|
|
325
|
+
*/
|
|
326
|
+
async function exchangeRpsTicketForUserToken(accessToken, proxiedFetch) {
|
|
327
|
+
const response = await proxiedFetch("https://user.auth.xboxlive.com/user/authenticate", {
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers: {
|
|
330
|
+
"Content-Type": "application/json",
|
|
331
|
+
Accept: "application/json",
|
|
332
|
+
"x-xbl-contract-version": "1",
|
|
333
|
+
},
|
|
334
|
+
body: JSON.stringify({
|
|
335
|
+
RelyingParty: "http://auth.xboxlive.com",
|
|
336
|
+
TokenType: "JWT",
|
|
337
|
+
Properties: {
|
|
338
|
+
AuthMethod: "RPS",
|
|
339
|
+
SiteName: "user.auth.xboxlive.com",
|
|
340
|
+
RpsTicket: `t=${accessToken}`,
|
|
341
|
+
},
|
|
342
|
+
}),
|
|
343
|
+
});
|
|
344
|
+
if (!response.ok) {
|
|
345
|
+
const text = await response.text();
|
|
346
|
+
throw new Error(`Xbox user token exchange failed: ${response.status} - ${text}`);
|
|
347
|
+
}
|
|
348
|
+
return response.json();
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Exchange Xbox User Token for XSTS Token
|
|
352
|
+
*/
|
|
353
|
+
async function exchangeTokenForXSTSToken(userToken, relyingParty, proxiedFetch) {
|
|
354
|
+
const response = await proxiedFetch("https://xsts.auth.xboxlive.com/xsts/authorize", {
|
|
355
|
+
method: "POST",
|
|
356
|
+
headers: {
|
|
357
|
+
"Content-Type": "application/json",
|
|
358
|
+
Accept: "application/json",
|
|
359
|
+
"x-xbl-contract-version": "1",
|
|
360
|
+
},
|
|
361
|
+
body: JSON.stringify({
|
|
362
|
+
RelyingParty: relyingParty,
|
|
363
|
+
TokenType: "JWT",
|
|
364
|
+
Properties: {
|
|
365
|
+
SandboxId: "RETAIL",
|
|
366
|
+
UserTokens: [userToken],
|
|
367
|
+
},
|
|
368
|
+
}),
|
|
369
|
+
});
|
|
370
|
+
if (!response.ok) {
|
|
371
|
+
const text = await response.text();
|
|
372
|
+
throw new Error(`Xbox XSTS token exchange failed: ${response.status} - ${text}`);
|
|
373
|
+
}
|
|
374
|
+
return response.json();
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Get Minecraft Bedrock authentication chains
|
|
378
|
+
*/
|
|
379
|
+
async function getMinecraftBedrockChains(xstsToken, userHash, clientPublicKey, proxiedFetch) {
|
|
380
|
+
const response = await proxiedFetch("https://multiplayer.minecraft.net/authentication", {
|
|
65
381
|
method: "POST",
|
|
66
382
|
headers: {
|
|
67
383
|
"Content-Type": "application/json",
|
|
@@ -72,6 +388,10 @@ async function getMinecraftBedrockChains(xstsToken, userHash, clientPublicKey) {
|
|
|
72
388
|
});
|
|
73
389
|
if (!response.ok) {
|
|
74
390
|
const text = await response.text();
|
|
391
|
+
if (response.status === 401) {
|
|
392
|
+
throw new Error("Minecraft Bedrock authentication failed (401).\n" +
|
|
393
|
+
"The account may not have an Xbox profile or Minecraft Bedrock Edition.");
|
|
394
|
+
}
|
|
75
395
|
throw new Error(`Minecraft Bedrock auth failed: ${response.status} - ${text}`);
|
|
76
396
|
}
|
|
77
397
|
const data = (await response.json());
|
package/dist/shared/auth.js
CHANGED
|
@@ -106,6 +106,8 @@ async function authenticateWithEmailPassword(client) {
|
|
|
106
106
|
email: client.options.email,
|
|
107
107
|
password: client.options.password,
|
|
108
108
|
clientPublicKey: client.data.loginData.clientX509,
|
|
109
|
+
cacheDir: client.options.tokensFolder,
|
|
110
|
+
proxy: client.options.proxy,
|
|
109
111
|
});
|
|
110
112
|
const profile = {
|
|
111
113
|
name: tokens.gamertag,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baltica",
|
|
3
3
|
"description": "Library for Minecraft Bedrock Edition community developers.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.22",
|
|
5
5
|
"minecraft": "1.21.130",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"license": "MIT",
|
|
@@ -21,18 +21,19 @@
|
|
|
21
21
|
}
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@sanctumterra/raknet": "^1.4.
|
|
24
|
+
"@sanctumterra/raknet": "^1.4.11",
|
|
25
25
|
"@serenityjs/binarystream": "^3.0.10",
|
|
26
|
-
"@serenityjs/protocol": "^0.8.
|
|
27
|
-
"
|
|
26
|
+
"@serenityjs/protocol": "^0.8.17",
|
|
27
|
+
"fetch-socks": "^1.3.2",
|
|
28
28
|
"jose": "^5.10.0",
|
|
29
29
|
"prismarine-auth": "^2.7.0",
|
|
30
|
+
"undici": "^7.18.2",
|
|
30
31
|
"uuid-1345": "^1.0.2"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@biomejs/biome": "1.9.4",
|
|
34
|
-
"@types/node": "^22.
|
|
35
|
+
"@types/node": "^22.19.7",
|
|
35
36
|
"@types/uuid-1345": "^0.99.25",
|
|
36
|
-
"typescript": "^5.9.
|
|
37
|
+
"typescript": "^5.9.3"
|
|
37
38
|
}
|
|
38
|
-
}
|
|
39
|
+
}
|