-
Notifications
You must be signed in to change notification settings - Fork 24
/
authentication.ts
212 lines (197 loc) · 6.73 KB
/
authentication.ts
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
import cp from 'child_process'
import util from 'util'
import { RIOT_GAMES_CERT } from './cert.js'
const exec = util.promisify<typeof cp.exec.__promisify__>(cp.exec)
const DEFAULT_NAME = 'LeagueClientUx'
const DEFAULT_POLL_INTERVAL = 2500
export interface Credentials {
/**
* The system port the LCU API is running on
*/
port: number
/**
* The password for the LCU API
*/
password: string
/**
* The system process id for the LeagueClientUx process
*/
pid: number
/**
* Riot Games' self-signed root certificate (contents of .pem). If
* it is `undefined` then unsafe authentication will be used.
*/
certificate?: string
}
export interface AuthenticationOptions {
/**
* League Client process name. Set to RiotClientUx if you would like to
* authenticate with the Riot Client
*
* Defaults: LeagueClientUx
*/
name?: string
/**
* Does not return before the League Client has been detected. This means the
* function stays unresolved until a League has been found.
*
* Defaults: false
*/
awaitConnection?: boolean
/**
* The time duration in milliseconds between each attempt to locate a League
* Client process. Has no effect if awaitConnection is false
*
* Default: 2500
*/
pollInterval?: number
/**
* Riot Games' self-signed root certificate (contents of .pem)
*
* Default: version of certificate bundled in package
*/
certificate?: string
/**
* Do not authenticate requests with Riot Games' self-signed root certificate
*
* Default: true if `certificate` is `undefined`
*/
unsafe?: boolean
/**
* Use deprecated Windows WMIC command line over Get-CimInstance. Does nothing
* if the system is not running on Windows. This is used to keep backwards
* compatability with Windows 7 systems that don't have Get-CimInstance
*
* See https://github.com/matsjla/league-connect/pull/54
* See https://github.com/matsjla/league-connect/pull/68
*
* Default: false
*/
useDeprecatedWmic?: boolean
/**
* Set the Windows shell to use.
*
* Default: 'powershell'
*/
windowsShell?: 'cmd' | 'powershell'
/**
* Debug mode. Prints error information to console.
* @internal
*/
__internalDebug?: boolean
}
/**
* Indicates that the application does not run on an environment that the
* League Client supports. The Client runs on windows, linux or darwin.
*/
export class InvalidPlatformError extends Error {
constructor() {
super('process runs on platform client does not support')
}
}
/**
* Indicates that the League Client could not be found
*/
export class ClientNotFoundError extends Error {
constructor() {
super('League Client process could not be located')
}
}
/**
* Indicates that the League Client is running as administrator and the current script is not
*/
export class ClientElevatedPermsError extends Error {
constructor() {
super('League Client has been detected but is running as administrator')
}
}
/**
* Locates a League Client and retrieves the credentials for the LCU API
* from the found process
*
* If options.awaitConnection is false the promise will resolve into a
* rejection if a League Client is not running
*
* @param {AuthenticationOptions} [options] Authentication options, if any
*
* @throws InvalidPlatformError If the environment is not running
* windows/linux/darwin
* @throws ClientNotFoundError If the League Client could not be found
* @throws ClientElevatedPermsError If the League Client is running as administrator and the script is not (Windows only)
*/
export async function authenticate(options?: AuthenticationOptions): Promise<Credentials> {
async function tryAuthenticate() {
const name = options?.name ?? DEFAULT_NAME
const portRegex = /--app-port=([0-9]+)(?= *"| --)/
const passwordRegex = /--remoting-auth-token=(.+?)(?= *"| --)/
const pidRegex = /--app-pid=([0-9]+)(?= *"| --)/
const isWindows = process.platform === 'win32'
let command: string
if (!isWindows) {
command = `ps x -o args | grep '${name}'`
} else if (isWindows && options?.useDeprecatedWmic === true) {
command = `wmic process where caption='${name}.exe' get commandline`
} else {
command = `Get-CimInstance -Query "SELECT * from Win32_Process WHERE name LIKE '${name}.exe'" | Select-Object -ExpandProperty CommandLine`
}
const executionOptions = isWindows ? { shell: options?.windowsShell ?? ('powershell' as string) } : {}
try {
const { stdout: rawStdout } = await exec(command, executionOptions)
// TODO: investigate regression with calling .replace on rawStdout
// Remove newlines from stdout
const stdout = rawStdout.replace(/\n|\r/g, '')
const [, port] = stdout.match(portRegex)!
const [, password] = stdout.match(passwordRegex)!
const [, pid] = stdout.match(pidRegex)!
const unsafe = options?.unsafe === true
const hasCert = options?.certificate !== undefined
// See flow chart for this here: https://github.com/matsjla/league-connect/pull/44#issuecomment-790384881
// If user specifies certificate, use it
const certificate = hasCert
? options!.certificate
: // Otherwise: does the user want unsafe requests?
unsafe
? undefined
: // Didn't specify, use our own certificate
RIOT_GAMES_CERT
return {
port: Number(port),
pid: Number(pid),
password,
certificate
}
} catch (err) {
if (options?.__internalDebug) console.error(err)
// Check if the user is running the client as an administrator leading to not being able to find the process
// Requires PowerShell 3.0 or higher
if (executionOptions.shell === 'powershell') {
const { stdout: isAdmin } = await exec(
`if ((Get-Process -Name ${name} -ErrorAction SilentlyContinue | Where-Object {!$_.Handle -and !$_.Path})) {Write-Output "True"} else {Write-Output "False"}`,
executionOptions
)
if (isAdmin.includes('True')) throw new ClientElevatedPermsError()
}
throw new ClientNotFoundError()
}
}
// Does not run windows/linux/darwin
if (!['win32', 'linux', 'darwin'].includes(process.platform)) {
throw new InvalidPlatformError()
}
if (options?.awaitConnection) {
// Poll until a client is found, attempting to resolve every
// `options.pollInterval` milliseconds
return new Promise(function self(resolve, reject) {
tryAuthenticate()
.then((result) => {
resolve(result)
})
.catch((err) => {
if (err instanceof ClientElevatedPermsError) reject(err)
setTimeout(self, options?.pollInterval ?? DEFAULT_POLL_INTERVAL, resolve, reject)
})
})
} else {
return tryAuthenticate()
}
}