From 995c9c03b7757b1f67d5ddc9905701d16acbea65 Mon Sep 17 00:00:00 2001 From: GreenAppers Date: Wed, 13 Nov 2024 22:17:34 -0800 Subject: [PATCH] Begin authentication --- package.json | 3 + src/components/App.tsx | 10 ++ src/constants.ts | 16 +++ src/main.ts | 17 +-- src/msal/AuthProvider.ts | 155 +++++++++++++++++++++ src/msal/CachePlugin.ts | 53 ++++++++ src/msal/CustomLoopbackClient.ts | 225 +++++++++++++++++++++++++++++++ src/preload.ts | 2 + src/store.ts | 40 +++++- src/utils/auth.ts | 213 +++++++++++++++++++++++++++++ src/utils/launcher.ts | 23 +++- webpack.plugins.ts | 2 + yarn.lock | 210 ++++++++++++++++++++++++++++- 13 files changed, 946 insertions(+), 23 deletions(-) create mode 100644 src/msal/AuthProvider.ts create mode 100644 src/msal/CachePlugin.ts create mode 100644 src/msal/CustomLoopbackClient.ts create mode 100644 src/utils/auth.ts diff --git a/package.json b/package.json index 7caf086..2fe736b 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "lint": "eslint --ext .ts,.tsx ." }, "dependencies": { + "@azure/msal-node": "^2.16.1", "@chakra-ui/icons": "^2.1.1", "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.13.3", @@ -55,6 +56,7 @@ "@electron-forge/plugin-webpack": "^7.5.0", "@electron-forge/publisher-github": "^7.5.0", "@electron/fuses": "^1.8.0", + "@types/dotenv-webpack": "^7.0.8", "@types/react": "^18.3.8", "@types/react-dom": "^18.3.0", "@types/split2": "^4.2.3", @@ -65,6 +67,7 @@ "@vercel/webpack-asset-relocator-loader": "1.7.3", "babel-loader": "^9.2.1", "css-loader": "^6.0.0", + "dotenv-webpack": "^8.1.0", "electron": "32.1.2", "eslint": "^8.0.1", "eslint-plugin-import": "^2.25.0", diff --git a/src/components/App.tsx b/src/components/App.tsx index 57d53f4..2e72175 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,4 +1,5 @@ import { + Button, Flex, IconButton, Image, @@ -9,6 +10,9 @@ import { TabPanels, Box, useColorModeValue, + Avatar, + Spacer, + Tooltip, } from '@chakra-ui/react' import React from 'react' import { Analytics } from './Analytics' @@ -41,6 +45,12 @@ function App() { Strongholds Waypoints + + diff --git a/src/constants.ts b/src/constants.ts index 155f6d9..ae59a57 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,9 @@ import { z } from 'zod' +import { + minecraftLoginResponse, + minecraftProfile, + xstsAuthorizeResponse, +} from './utils/auth' export enum ModLoaderName { Fabric = 'Fabric', @@ -113,6 +118,15 @@ export const fabricVersionDetails = z.object({ ), }) +export const gameAccount = z.object({ + active: z.boolean(), + profile: minecraftProfile, + userToken: xstsAuthorizeResponse, + xboxliveToken: xstsAuthorizeResponse, + minecraftToken: xstsAuthorizeResponse, + yggdrasilToken: minecraftLoginResponse, +}) + export const gameInstall = z.object({ name: z.string(), path: z.string(), @@ -129,6 +143,7 @@ export type MojangVersionManifest = z.infer export type MojangVersionManifests = z.infer export type MojangStringsTemplate = z.infer +export type GameAccount = z.infer export type GameInstall = z.infer export type StoreSchema = { @@ -154,6 +169,7 @@ export const CHANNELS = { electronStoreGet: 'electron-store-get', electronStoreSet: 'electron-store-set', launchGameInstall: 'launch-game-install', + loginToMicrosoftAccount: 'login-to-microsoft-account', openBrowserWindow: 'open-browser-window', openFileDialog: 'open-file-dialog', getLogfilePath: 'get-logfile-path', diff --git a/src/main.ts b/src/main.ts index bb5608e..17a1c66 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,7 +13,8 @@ import log from 'electron-log/main' import './index.css' import { CHANNELS, GameInstall, LAUNCH_CHANNEL, STORE_KEYS } from './constants' -import { newStore } from './store' +import { AuthProvider } from './msal/AuthProvider' +import { newStore, removeGameInstall, updateGameInstall } from './store' import { findGameLogFiles, newGameLogContext, @@ -29,6 +30,7 @@ declare const MAIN_WINDOW_WEBPACK_ENTRY: string declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string const clipboardPollInterval = 500 +const authProvider = new AuthProvider() const gameLogContext = newGameLogContext() const store = newStore() @@ -98,21 +100,14 @@ app.on('ready', () => { CHANNELS.createGameInstall, async (_event, gameInstall: GameInstall) => { await setupInstall(gameInstall) - const gameInstalls = store - .get(STORE_KEYS.gameInstalls) - .filter((x) => x.uuid !== gameInstall.uuid) - gameInstalls.push({ ...gameInstall }) - store.set(STORE_KEYS.gameInstalls, gameInstalls) + updateGameInstall(store, gameInstall) return gameInstall } ) ipcMain.handle( CHANNELS.deleteGameInstall, async (_event, gameInstall: GameInstall) => { - const gameInstalls = store - .get(STORE_KEYS.gameInstalls) - .filter((x) => x.uuid !== gameInstall.uuid) - store.set(STORE_KEYS.gameInstalls, gameInstalls) + removeGameInstall(store, gameInstall) return true } ) @@ -120,7 +115,7 @@ app.on('ready', () => { CHANNELS.launchGameInstall, async (_event, launchId: string, gameInstall: GameInstall) => { const channel = LAUNCH_CHANNEL(launchId) - launchInstall(launchId, gameInstall, (update) => { + launchInstall(launchId, gameInstall, authProvider, store, (update) => { mainWindow?.webContents.send(channel, update) }).catch((error) => log.error('launchGameInstall error', error)) return true diff --git a/src/msal/AuthProvider.ts b/src/msal/AuthProvider.ts new file mode 100644 index 0000000..08d2d16 --- /dev/null +++ b/src/msal/AuthProvider.ts @@ -0,0 +1,155 @@ +import { app, shell } from 'electron' +import { + PublicClientApplication, + LogLevel, + AccountInfo, + AuthenticationResult, + InteractiveRequest, + InteractionRequiredAuthError, +} from '@azure/msal-node' +import log from 'electron-log/main' +import path from 'path' + +import { cachePlugin } from './CachePlugin' +import { CustomLoopbackClient } from './CustomLoopbackClient' + +const openBrowser = (url: string) => shell.openExternal(url) + +export class AuthProvider { + private clientApplication: PublicClientApplication + private account: AccountInfo | null + + constructor() { + this.clientApplication = new PublicClientApplication({ + auth: { + authority: 'https://login.microsoftonline.com/consumers/', + clientId: process.env.MICROSOFT_ENTRA_CLIENT_ID ?? '', + }, + cache: { + cachePlugin: cachePlugin( + path.join(app.getPath('userData'), 'auth.json') + ), + }, + system: { + loggerOptions: { + loggerCallback: (level, message) => { + switch (level) { + case LogLevel.Error: + log.error(message) + break + case LogLevel.Warning: + log.warn(message) + break + case LogLevel.Info: + log.info(message) + break + case LogLevel.Verbose: + log.verbose(message) + break + case LogLevel.Trace: + log.debug(message) + break + } + }, + logLevel: LogLevel.Info, + piiLoggingEnabled: false, + }, + }, + }) + } + + async login(): Promise { + const authResult = await this.getToken() + return this.handleResponse(authResult) + } + + async logout(): Promise { + try { + if (!this.account) return + await this.clientApplication.getTokenCache().removeAccount(this.account) + this.account = null + } catch (error) { + console.log(error) + } + } + + async getToken( + scopes = ['XboxLive.signin', 'XboxLive.offline_access'] + ): Promise { + let authResponse: AuthenticationResult | undefined + const account = this.account || (await this.getAccount()) + if (account) { + try { + authResponse = await this.clientApplication.acquireTokenSilent({ + account, + scopes, + }) + } catch (error) { + if (!(error instanceof InteractionRequiredAuthError)) throw error + } + } + if (!authResponse) { + authResponse = await this.getTokenInteractive({ openBrowser, scopes }) + } + this.account = authResponse.account + return authResponse + } + + async getTokenInteractive( + tokenRequest: InteractiveRequest + ): Promise { + /** + * A loopback server of your own implementation, which can have custom logic + * such as attempting to listen on a given port if it is available. + */ + const customLoopbackClient = await CustomLoopbackClient.initialize(3874) + + const interactiveRequest: InteractiveRequest = { + ...tokenRequest, + successTemplate: + '

Successfully signed in!

You can close this window now.

', + errorTemplate: + '

Oops! Something went wrong

Check the console for more information.

', + loopbackClient: customLoopbackClient, // overrides default loopback client + } + + const authResponse = await this.clientApplication.acquireTokenInteractive( + interactiveRequest + ) + return authResponse + } + + /** + * Handles the response from a popup or redirect. If response is null, will check if we have any accounts and attempt to sign in. + * @param response + */ + private async handleResponse(response: AuthenticationResult) { + this.account = response?.account || (await this.getAccount()) + return response + } + + public currentAccount(): AccountInfo | null { + return this.account + } + + private async getAccount(): Promise { + const cache = this.clientApplication.getTokenCache() + const currentAccounts = await cache.getAllAccounts() + + if (currentAccounts === null) { + console.log('No accounts detected') + return null + } + + if (currentAccounts.length > 1) { + console.log( + 'Multiple accounts detected, need to add choose account code.' + ) + return currentAccounts[0] + } else if (currentAccounts.length === 1) { + return currentAccounts[0] + } else { + return null + } + } +} diff --git a/src/msal/CachePlugin.ts b/src/msal/CachePlugin.ts new file mode 100644 index 0000000..6118f14 --- /dev/null +++ b/src/msal/CachePlugin.ts @@ -0,0 +1,53 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { ICachePlugin, TokenCacheContext } from '@azure/msal-node' +import fs from 'fs' + +export const cachePlugin = (CACHE_LOCATION: string): ICachePlugin => { + const beforeCacheAccess = async (cacheContext: TokenCacheContext) => { + return new Promise((resolve, reject) => { + if (fs.existsSync(CACHE_LOCATION)) { + fs.readFile(CACHE_LOCATION, 'utf-8', (err, data) => { + if (err) { + reject() + } else { + cacheContext.tokenCache.deserialize(data) + resolve() + } + }) + } else { + fs.writeFile( + CACHE_LOCATION, + cacheContext.tokenCache.serialize(), + (err) => { + if (err) { + reject() + } + } + ) + } + }) + } + + const afterCacheAccess = async (cacheContext: TokenCacheContext) => { + if (cacheContext.cacheHasChanged) { + fs.writeFile( + CACHE_LOCATION, + cacheContext.tokenCache.serialize(), + (err) => { + if (err) { + console.log(err) + } + } + ) + } + } + + return { + beforeCacheAccess, + afterCacheAccess, + } +} diff --git a/src/msal/CustomLoopbackClient.ts b/src/msal/CustomLoopbackClient.ts new file mode 100644 index 0000000..83e583e --- /dev/null +++ b/src/msal/CustomLoopbackClient.ts @@ -0,0 +1,225 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import http from 'http' +import { + ILoopbackClient, + ServerAuthorizationCodeResponse, +} from '@azure/msal-node' + +/** + * Implements ILoopbackClient interface to listen for authZ code response. + * This custom implementation checks for a preferred port and uses it if available. + * If the preferred port is not available, it will use a random available port. + */ +export class CustomLoopbackClient implements ILoopbackClient { + path = '/auth' + port = 0 // default port, which will be set to a random available port + private server: http.Server + + private constructor(port = 0) { + this.port = port + } + + /** + * Initializes a loopback server with an available port + * @param preferredPort + * @param logger + * @returns + */ + static async initialize( + preferredPort: number | undefined + ): Promise { + const loopbackClient = new CustomLoopbackClient() + + if (preferredPort === 0 || preferredPort === undefined) { + return loopbackClient + } + const isPortAvailable = await loopbackClient.isPortAvailable(preferredPort) + + if (isPortAvailable) { + loopbackClient.port = preferredPort + } + + return loopbackClient + } + + /** + * Spins up a loopback server which returns the server response when the localhost redirectUri is hit + * @param successTemplate + * @param errorTemplate + * @returns + */ + async listenForAuthCode( + successTemplate?: string, + errorTemplate?: string + ): Promise { + if (this.server) { + throw new Error('Loopback server already exists. Cannot create another.') + } + + const authCodeListener = new Promise( + (resolve, reject) => { + this.server = http.createServer( + async (req: http.IncomingMessage, res: http.ServerResponse) => { + const url = req.url + if (!url) { + res.end(errorTemplate || 'Error occurred loading redirectUrl') + reject( + new Error( + 'Loopback server callback was invoked without a url. This is unexpected.' + ) + ) + return + } else if (url === this.path) { + res.end( + successTemplate || + 'Auth code was successfully acquired. You can close this window now.' + ) + return + } + + const authCodeResponse = + CustomLoopbackClient.getDeserializedQueryString(url) + if (authCodeResponse.code) { + const redirectUri = await this.getRedirectUri() + res.writeHead(302, { location: redirectUri }) // Prevent auth code from being saved in the browser history + res.end() + } + resolve(authCodeResponse) + } + ) + this.server.listen(this.port) + } + ) + + // Wait for server to be listening + await new Promise((resolve) => { + let ticks = 0 + const id = setInterval(() => { + if (5000 / 100 < ticks) { + throw new Error( + 'Timed out waiting for auth code listener to be registered.' + ) + } + + if (this.server.listening) { + clearInterval(id) + resolve() + } + ticks++ + }, 100) + }) + + return authCodeListener + } + + /** + * Get the port that the loopback server is running on + * @returns + */ + getRedirectUri(): string { + if (!this.server) { + throw new Error('No loopback server exists yet.') + } + + const address = this.server.address() + if (!address || typeof address === 'string' || !address.port) { + this.closeServer() + throw new Error( + 'Loopback server address is not type string. This is unexpected.' + ) + } + + const port = address && address.port + + return `http://localhost:${port}${this.path}` + } + + /** + * Close the loopback server + */ + closeServer(): void { + if (this.server) { + this.server.close() + } + } + + /** + * Attempts to create a server and listen on a given port + * @param port + * @returns + */ + isPortAvailable(port: number): Promise { + return new Promise((resolve) => { + const server = http + .createServer() + .listen(port, () => { + server.close() + resolve(true) + }) + .on('error', () => { + resolve(false) + }) + }) + } + + /** + * Returns URL query string as server auth code response object. + */ + static getDeserializedQueryString( + query: string + ): ServerAuthorizationCodeResponse { + // Check if given query is empty + if (!query) { + return {} + } + // Strip the ? symbol if present + const parsedQueryString = this.parseQueryString(query) + // If ? symbol was not present, above will return empty string, so give original query value + const deserializedQueryString: ServerAuthorizationCodeResponse = + this.queryStringToObject(parsedQueryString || query) + // Check if deserialization didn't work + if (!deserializedQueryString) { + throw 'Unable to deserialize query string' + } + return deserializedQueryString + } + + /** + * Parses query string from given string. Returns empty string if no query symbol is found. + * @param queryString + */ + static parseQueryString(queryString: string): string { + const queryIndex1 = queryString.indexOf('?') + const queryIndex2 = queryString.indexOf('/?') + if (queryIndex2 > -1) { + return queryString.substring(queryIndex2 + 2) + } else if (queryIndex1 > -1) { + return queryString.substring(queryIndex1 + 1) + } + return '' + } + + /** + * Parses string into an object. + * + * @param query + */ + static queryStringToObject(query: string): ServerAuthorizationCodeResponse { + const obj: { [key: string]: string } = {} + const params = query.split('&') + const decode = (s: string) => decodeURIComponent(s.replace(/\+/g, ' ')) + params.forEach((pair) => { + if (pair.trim()) { + const [key, value] = pair.split(/=(.+)/g, 2) // Split on the first occurence of the '=' character + if (key && value) { + obj[decode(key)] = decode(value) + } + } + }) + return obj as ServerAuthorizationCodeResponse + } +} diff --git a/src/preload.ts b/src/preload.ts index 06f9068..2fb7fb6 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -36,6 +36,8 @@ export const api = { ipcRenderer.on(channel, listener) return { channel, listener } }, + loginToMicrosoftAccount: () => + ipcRenderer.send(CHANNELS.loginToMicrosoftAccount), openBrowserWindow: (url: string) => ipcRenderer.send(CHANNELS.openBrowserWindow, url), openFileDialog: (path: string) => diff --git a/src/store.ts b/src/store.ts index 9edd76a..37d7f32 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,5 +1,10 @@ import Store, { Schema } from 'electron-store' -import type { StoreSchema } from './constants' +import { + GameAccount, + GameInstall, + STORE_KEYS, + type StoreSchema, +} from './constants' import { getDefaultGameLogDirectories } from './utils/gamelog' export const schema: Schema = { @@ -35,3 +40,36 @@ export const schema: Schema = { } export const newStore = () => new Store({ schema }) + +export const updateGameAccount = ( + store: Store, + gameAccount: GameAccount +) => + store.set(STORE_KEYS.gameAccounts, [ + ...store + .get(STORE_KEYS.gameAccounts) + .filter((x) => x.profile.id !== gameAccount.profile.id), + ...[{ ...gameAccount }], + ]) + +export const updateGameInstall = ( + store: Store, + gameInstall: GameInstall +) => + store.set(STORE_KEYS.gameInstalls, [ + ...store + .get(STORE_KEYS.gameInstalls) + .filter((x) => x.uuid !== gameInstall.uuid), + ...[{ ...gameInstall }], + ]) + +export const removeGameInstall = ( + store: Store, + gameInstall: GameInstall +) => + store.set( + STORE_KEYS.gameInstalls, + store + .get(STORE_KEYS.gameInstalls) + .filter((x) => x.uuid !== gameInstall.uuid) + ) diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..5931ff0 --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,213 @@ +import axios from 'axios' +import Store from 'electron-store' +import { z } from 'zod' +import type { GameAccount, StoreSchema } from '../constants' +import { updateGameAccount } from '../store' + +// References: +// - https://mojang-api-docs.gapple.pw/ +// - https://minecraft-launcher-lib.readthedocs.io/en/stable/tutorial/microsoft_login.html +// - https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/commerce/service-to-service/xstore-requesting-a-userstoreid-from-services + +export const minecraftLoginResponse = z.object({ + username: z.string(), + roles: z.array(z.string()), + access_token: z.string(), + token_type: z.string(), + expires_in: z.number(), +}) + +export const minecraftProfileState = z.enum(['ACTIVE', 'INACTIVE']) +export const minecraftSkinVariant = z.enum(['SLIM', 'CLASSIC']) + +export const minecraftSkin = z.object({ + id: z.string(), + state: minecraftProfileState, + url: z.string(), + variant: minecraftSkinVariant, +}) + +export const minecraftCape = z.object({ + id: z.string(), + state: minecraftProfileState, + url: z.string(), + alias: z.string(), +}) + +export const minecraftProfile = z.object({ + id: z.string(), + name: z.string(), + skins: z.array(minecraftSkin), + capes: z.array(minecraftCape), +}) + +export const xboxLiveProfile = z.object({ + profileUsers: z.array( + z.object({ + id: z.string(), + hostId: z.optional(z.string()).nullable(), + settings: z.array( + z.object({ + id: z.string(), + value: z.string(), + }) + ), + isSponsoredUser: z.boolean(), + }) + ), +}) + +export const xstsAuthorizeResponse = z.object({ + IssueInstant: z.string(), + NotAfter: z.string(), + Token: z.string(), + DisplayClaims: z.object({ + xui: z.array( + z.object({ + gtg: z.optional(z.string()), + uhs: z.string(), + xid: z.optional(z.string()), + }) + ), + }), +}) + +export type MinecraftLauncherLoginResponse = z.infer< + typeof minecraftLoginResponse +> +export type XSTSAuthorizeResponse = z.infer + +export const formatXSTSToken = (uhs: string, token: string) => + `XBL3.0 x=${uhs};${token}` + +export const postXSTSUserAuthenticate = (microsoftToken: string) => + axios + .post('https://user.auth.xboxlive.com/user/authenticate', { + Properties: { + AuthMethod: 'RPS', + SiteName: 'user.auth.xboxlive.com', + RpsTicket: `d=${microsoftToken}`, + }, + RelyingParty: 'http://auth.xboxlive.com', + TokenType: 'JWT', + }) + .then((response) => xstsAuthorizeResponse.parse(response.data)) + +export const postXSTSAuthorize = ( + userToken: string, + relyingParty: 'rp://api.minecraftservices.com/' | 'http://xboxlive.com' +) => + axios + .post('https://xsts.auth.xboxlive.com/xsts/authorize', { + Properties: { + SandboxId: 'RETAIL', + UserTokens: [userToken], + }, + RelyingParty: relyingParty, + TokenType: 'JWT', + }) + .then((response) => xstsAuthorizeResponse.parse(response.data)) + +export const postMinecraftLauncherLogin = (uhs: string, token: string) => + axios + .post('https://api.minecraftservices.com/launcher/login', { + xtoken: formatXSTSToken(uhs, token), + platform: 'PC_LAUNCHER', + }) + .then((response) => minecraftLoginResponse.parse(response.data)) + +export const postMinecraftLoginWithXBox = (uhs: string, token: string) => + axios + .post('https://api.minecraftservices.com/authentication/login_with_xbox', { + identityToken: formatXSTSToken(uhs, token), + ensureLegacyEnabled: true, + }) + .then((response) => minecraftLoginResponse.parse(response.data)) + +export const getXBoxLiveProfile = (uhs: string, token: string) => + axios + .get( + 'https://profile.xboxlive.com/users/me/profile/settings?settings=' + + encodeURIComponent( + 'GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,' + + 'PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag,ModernGamertagSuffix,' + + 'UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep,' + + 'PreferredColor,Location,Bio,Watermarks,' + + 'RealName,RealNameOverride,IsQuarantined' + ), + { + headers: { + 'x-xbl-contract-version': '3', + Authorization: formatXSTSToken(uhs, token), + }, + } + ) + .then((response) => xboxLiveProfile.parse(response.data)) + +export const getMinecraftProfile = (token: string) => + axios + .get('https://api.minecraftservices.com/minecraft/profile', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((response) => minecraftProfile.parse(response.data)) + +export async function loginToMinecraft( + microsoftToken: string | undefined, + store: Store +): Promise { + if (!microsoftToken) throw new Error('Microsoft token is required') + const xboxLiveLogin = await postXSTSUserAuthenticate(microsoftToken) + console.log('got xbixLiveLogin', xboxLiveLogin) + + const xboxLiveAuth = await postXSTSAuthorize( + xboxLiveLogin.Token, + 'http://xboxlive.com' + ) + + console.log( + 'got xboxLiveAuth', + xboxLiveAuth, + xboxLiveAuth.DisplayClaims.xui[0], + xboxLiveAuth.Token + ) + + const minecraftAuth = await postXSTSAuthorize( + xboxLiveLogin.Token, + 'rp://api.minecraftservices.com/' + ) + console.log( + 'got minecraftAuth', + minecraftAuth, + minecraftAuth.DisplayClaims.xui[0], + minecraftAuth.Token + ) + + const profile = await getXBoxLiveProfile( + xboxLiveLogin.DisplayClaims.xui[0].uhs, + xboxLiveAuth.Token + ) + console.log('got profile', profile, JSON.stringify(profile)) + + const login = await postMinecraftLoginWithXBox( + minecraftAuth.DisplayClaims.xui[0].uhs, + minecraftAuth.Token + ) + console.log('got login', login) + + const minecraftProfile = await getMinecraftProfile(login.access_token) + console.log('got minecraft profile', minecraftProfile) + + const gameAccount: GameAccount = { + active: true, + profile: minecraftProfile, + userToken: xboxLiveLogin, + xboxliveToken: xboxLiveAuth, + minecraftToken: minecraftAuth, + yggdrasilToken: login, + } + updateGameAccount(store, gameAccount) + + return gameAccount +} diff --git a/src/utils/launcher.ts b/src/utils/launcher.ts index 5a14f01..a319596 100644 --- a/src/utils/launcher.ts +++ b/src/utils/launcher.ts @@ -1,3 +1,5 @@ +import axios from 'axios' +import Store from 'electron-store' import { spawn } from 'child_process' import { app } from 'electron' import fs from 'fs' @@ -10,8 +12,11 @@ import { GameInstall, mojangVersionDetails, parseLibraryName, + StoreSchema, updateVersionDetailsLibrary, } from '../constants' +import { AuthProvider } from '../msal/AuthProvider' +import { loginToMinecraft } from './auth' import { checkFileExists, download, @@ -25,7 +30,6 @@ import { getOsArch, getOsName, } from './template' -import axios from 'axios' const launchRunning: Record = {} @@ -149,10 +153,7 @@ export async function updateInstall(install: GameInstall) { for (const mod of install.mods ?? []) { const url = new URL(mod) downloadLibraries.push(() => - downloadIfMissing( - mod, - path.join(modsPath, path.basename(url.pathname)) - ) + downloadIfMissing(mod, path.join(modsPath, path.basename(url.pathname))) ) } @@ -163,6 +164,8 @@ export async function updateInstall(install: GameInstall) { export async function launchInstall( launchId: string, install: GameInstall, + authProvider: AuthProvider, + store: Store, callback: (updated: string) => void ) { try { @@ -175,6 +178,14 @@ export async function launchInstall( callback('Updating install') const versionDetails = await updateInstall(install) + callback('Authenticating') + const microsoftAuth = await authProvider.login() + const gameAccount = await loginToMinecraft( + microsoftAuth?.accessToken, + store + ) + const accessToken = gameAccount.yggdrasilToken.access_token + const osArch = getOsArch() const osName = getOsName() const librariesPath = getLibrariesPath() @@ -185,7 +196,7 @@ export async function launchInstall( assets_root: '', assets_index_name: '1.21', auth_uuid: '', - auth_access_token: '0', + auth_access_token: accessToken || '0', clientid: '', auth_xuid: '', user_type: '', diff --git a/webpack.plugins.ts b/webpack.plugins.ts index 846aa24..315201b 100644 --- a/webpack.plugins.ts +++ b/webpack.plugins.ts @@ -1,3 +1,4 @@ +import Dotenv from 'dotenv-webpack' import type IForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -7,4 +8,5 @@ export const plugins = [ new ForkTsCheckerWebpackPlugin({ logger: 'webpack-infrastructure', }), + new Dotenv(), ]; diff --git a/yarn.lock b/yarn.lock index 48c86c3..051b385 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,20 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@azure/msal-common@14.16.0": + version "14.16.0" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.16.0.tgz#f3470fcaec788dbe50859952cd499340bda23d7a" + integrity sha512-1KOZj9IpcDSwpNiQNjt0jDYZpQvNZay7QAEi/5DLubay40iGYtLzya/jbjRPLyOTZhEKyL1MzPuw2HqBCjceYA== + +"@azure/msal-node@^2.16.1": + version "2.16.1" + resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.16.1.tgz#89828832e8e6c8a88cecc4ef6d8d4e4352116b77" + integrity sha512-1NEFpTmMMT2A7RnZuvRl/hUmJU+GLPjh+ShyIqPktG2PvSd2yvPnzGd/BxIBAAvJG5nr9lH4oYcQXepDbaE7fg== + dependencies: + "@azure/msal-common" "14.16.0" + jsonwebtoken "^9.0.0" + uuid "^8.3.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" @@ -1983,7 +1997,32 @@ resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== -"@types/estree@^1.0.5": +"@types/dotenv-webpack@^7.0.8": + version "7.0.8" + resolved "https://registry.yarnpkg.com/@types/dotenv-webpack/-/dotenv-webpack-7.0.8.tgz#c4685d96423a26270d845d18d31143d50698138c" + integrity sha512-JiLJdF2y9dW0eJjKUBlVnIwz/Raej00MOMdKcUuVxYJquvcuMgQdJw7cnN+hRZ7eejYgKr8kZsIw4nihwYYGeA== + dependencies: + "@types/node" "*" + tapable "^2.2.0" + webpack "^5" + +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.5", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== @@ -2045,7 +2084,7 @@ dependencies: "@types/node" "*" -"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -2503,6 +2542,11 @@ acorn@^8.11.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== +acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -2901,11 +2945,26 @@ browserslist@^4.21.10, browserslist@^4.23.1: node-releases "^2.0.18" update-browserslist-db "^1.1.0" +browserslist@^4.24.0: + version "4.24.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== + dependencies: + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" + node-releases "^2.0.18" + update-browserslist-db "^1.1.1" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -3000,6 +3059,11 @@ caniuse-lite@^1.0.30001646: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz#1529a723505e429fdfd49532e9fc42273ba7fed7" integrity sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA== +caniuse-lite@^1.0.30001669: + version "1.0.30001680" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz#5380ede637a33b9f9f1fc6045ea99bd142f3da5e" + integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA== + chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -3740,11 +3804,37 @@ dot-prop@^6.0.1: dependencies: is-obj "^2.0.0" +dotenv-defaults@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz#6b3ec2e4319aafb70940abda72d3856770ee77ac" + integrity sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg== + dependencies: + dotenv "^8.2.0" + +dotenv-webpack@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-8.1.0.tgz#4d66abc4a30395b46a030ebcd125320232b54873" + integrity sha512-owK1JcsPkIobeqjVrk6h7jPED/W6ZpdFsMPR+5ursB7/SdgDyO+VzAU+szK8C8u3qUhtENyYnj8eyXMR5kkGag== + dependencies: + dotenv-defaults "^2.0.2" + +dotenv@^8.2.0: + version "8.6.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" + integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -3824,6 +3914,11 @@ electron-to-chromium@^1.5.4: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz#5203ce5d6054857d84ba84d3681cbe59132ade78" integrity sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw== +electron-to-chromium@^1.5.41: + version "1.5.57" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.57.tgz#cb43af8784166bca24565b3418bf5f775a6b1c86" + integrity sha512-xS65H/tqgOwUBa5UmOuNSLuslDo7zho0y/lgQw35pnrqiZh7UOWHCeL/Bt6noJATbA6tpQJGCifsFsIRZj1Fqg== + electron-winstaller@^5.3.0: version "5.4.0" resolved "https://registry.yarnpkg.com/electron-winstaller/-/electron-winstaller-5.4.0.tgz#f0660d476d5c4f579fdf7edd2f0cf01d54c4d0b2" @@ -4021,7 +4116,7 @@ es6-error@^4.1.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -escalade@^3.1.1, escalade@^3.1.2: +escalade@^3.1.1, escalade@^3.1.2, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== @@ -5552,11 +5647,44 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonwebtoken@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + junk@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + keyv@^4.0.0, keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -5668,6 +5796,36 @@ lodash.get@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -5678,6 +5836,11 @@ lodash.mergewith@4.6.2: resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -7073,7 +7236,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -7912,6 +8075,14 @@ update-browserslist-db@^1.1.0: escalade "^3.1.2" picocolors "^1.0.1" +update-browserslist-db@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -7957,7 +8128,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^8.3.2: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -8088,6 +8259,35 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== +webpack@^5: + version "5.96.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.96.1.tgz#3676d1626d8312b6b10d0c18cc049fba7ac01f0c" + integrity sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA== + dependencies: + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.6" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" + acorn "^8.14.0" + browserslist "^4.24.0" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.1" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + webpack@^5.69.1: version "5.94.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f"