@metaplay/metaplay-auth 1.7.0 → 1.7.1

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/index.ts CHANGED
@@ -89,7 +89,7 @@ interface PortalEnvironmentInfo {
89
89
  * @param environment Environment slug.
90
90
  * @returns The portal's information about the environment.
91
91
  */
92
- // eslint-disable-next-line @typescript-eslint/max-params
92
+
93
93
  async function fetchManagedEnvironmentInfoWithSlugs(
94
94
  tokens: TokenSet,
95
95
  organization: string,
@@ -154,7 +154,7 @@ async function fetchManageEnvironmentInfoWithHumanId(tokens: TokenSet, humanId:
154
154
  * @param environment Environment slug.
155
155
  * @returns The TargetEnvironment instance needed to operate with the environment.
156
156
  */
157
- // eslint-disable-next-line @typescript-eslint/max-params
157
+
158
158
  async function resolveTargetEnvironmentFromSlugs(
159
159
  tokens: TokenSet,
160
160
  organization: string,
@@ -630,7 +630,7 @@ program
630
630
  reject(error)
631
631
  } else {
632
632
  // result contains an array of all the progress objects
633
- logger.debug('Succesfully finished pushing docker image')
633
+ logger.debug('Successfully finished pushing docker image')
634
634
  resolve(result)
635
635
  }
636
636
  },
@@ -749,7 +749,7 @@ program
749
749
  reject(error)
750
750
  } else {
751
751
  // result contains an array of all the progress objects
752
- logger.debug('Succesfully finished pulling image')
752
+ logger.debug('Successfully finished pulling image')
753
753
  resolve(result)
754
754
  }
755
755
  },
@@ -790,14 +790,16 @@ program
790
790
  options.helmChartRepo ?? imageLabels['io.metaplay.default_helm_repo'] ?? 'https://charts.metaplay.dev'
791
791
  )
792
792
 
793
+ // Warn about Helm chart version needing to be specified explicitly (unless using a local chart)
794
+ if (!options.helmChartVersion && !options.localChartPath) {
795
+ console.warn('Warning: You should specify the Helm chart version explicitly with --helm-chart-version=<version>!')
796
+ }
797
+
793
798
  // Resolve helmChartVersion, in order of precedence:
794
799
  // - Specified in the cli options
795
800
  // - Specified in the docker image label
796
801
  // - Unknown, error out!
797
802
  const helmChartVersionSpec = options.helmChartVersion ?? imageLabels['io.metaplay.default_server_chart_version']
798
- if (!options.helmChartVersion) {
799
- console.warn('Warning: You should specify the Helm chart version with --helm-chart-version=<version>!')
800
- }
801
803
  if (!helmChartVersionSpec) {
802
804
  throw new Error('No Helm chart version defined. With pre-R28 SDK versions, you must specify the Helm chart version explicitly with --helm-chart-version=<version>.')
803
805
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@metaplay/metaplay-auth",
3
3
  "description": "Utility CLI for authenticating with the Metaplay Auth and making authenticated calls to infrastructure endpoints.",
4
- "version": "1.7.0",
4
+ "version": "1.7.1",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
7
7
  "homepage": "https://metaplay.io",
@@ -18,22 +18,22 @@
18
18
  "devDependencies": {
19
19
  "@metaplay/eslint-config": "workspace:*",
20
20
  "@types/dockerode": "3.3.31",
21
- "@types/express": "4.17.21",
21
+ "@types/express": "5.0.0",
22
22
  "@types/js-yaml": "4.0.9",
23
23
  "@types/jsonwebtoken": "9.0.7",
24
24
  "@types/jwk-to-pem": "2.0.3",
25
- "@types/node": "20.16.1",
25
+ "@types/node": "20.16.10",
26
26
  "@types/semver": "7.5.8",
27
27
  "esbuild": "0.24.0",
28
28
  "tsx": "4.19.1",
29
- "typescript": "5.6.2",
30
- "vitest": "2.1.1",
31
- "@aws-sdk/client-ecr": "3.654.0",
32
- "@kubernetes/client-node": "1.0.0-rc6",
33
- "@ory/client": "1.15.4",
29
+ "typescript": "5.6.3",
30
+ "vitest": "2.1.3",
31
+ "@aws-sdk/client-ecr": "3.675.0",
32
+ "@kubernetes/client-node": "1.0.0-rc7",
33
+ "@ory/client": "1.15.7",
34
34
  "commander": "12.1.0",
35
35
  "dockerode": "4.0.2",
36
- "h3": "1.12.0",
36
+ "h3": "1.13.0",
37
37
  "js-yaml": "4.1.0",
38
38
  "jsonwebtoken": "9.0.2",
39
39
  "jwk-to-pem": "2.0.6",
package/src/auth.ts CHANGED
@@ -1,4 +1,4 @@
1
- /* eslint-disable @typescript-eslint/no-misused-promises */
1
+
2
2
  import { toNodeListener, createApp, defineEventHandler, getQuery, sendError } from 'h3'
3
3
  import jwt from 'jsonwebtoken'
4
4
  import jwkToPem from 'jwk-to-pem'
@@ -324,7 +324,7 @@ async function extendCurrentSessionWithRefreshToken(
324
324
  * @param code
325
325
  * @returns
326
326
  */
327
- // eslint-disable-next-line @typescript-eslint/max-params
327
+
328
328
  async function getTokensWithAuthorizationCode(
329
329
  state: string,
330
330
  redirectUri: string,
@@ -356,7 +356,7 @@ async function getTokensWithAuthorizationCode(
356
356
  */
357
357
  export async function loadTokens(): Promise<TokenSet> {
358
358
  try {
359
- const tokens = (await getSecret('tokens')) as TokenSet
359
+ const tokens = (await getSecret('tokens')) as unknown as TokenSet
360
360
 
361
361
  if (!tokens) {
362
362
  throw new Error('Unable to load tokens. You need to login first.')
package/src/deployment.ts CHANGED
@@ -3,7 +3,8 @@ import os from 'os'
3
3
  import path from 'path'
4
4
  import { exit } from 'process'
5
5
  import { EnvironmentDetails, TargetEnvironment } from 'targetenvironment.js'
6
- import * as net from 'net'
6
+ import { promises as dns } from 'dns'
7
+ import tls from 'tls'
7
8
 
8
9
  import {
9
10
  KubeConfig,
@@ -404,8 +405,8 @@ export async function waitForGameServerPodsToBeReady(
404
405
 
405
406
  // Handle state of the deployment
406
407
  if (anyPodsInPhase(podStatuses, GameServerPodPhase.Failed)) {
407
- logger.error(`Gameserver start failed with pod statuses: ${JSON.stringify(podStatuses, undefined, 2)}`)
408
- console.log('Gameserver failed to start due to the pods not starting properly! See above for details.')
408
+ logger.error(`Game server start failed with pod statuses: ${JSON.stringify(podStatuses, undefined, 2)}`)
409
+ console.log('Game server failed to start due to the pods not starting properly! See above for details.')
409
410
  for (let ndx = 0; ndx < pods.length; ndx += 1) {
410
411
  const status = podStatuses[ndx]
411
412
  const suffix = status.phase !== GameServerPodPhase.Ready ? ` -- ${status.message}` : ''
@@ -417,14 +418,14 @@ export async function waitForGameServerPodsToBeReady(
417
418
  anyPodsInPhase(podStatuses, GameServerPodPhase.Pending) ||
418
419
  anyPodsInPhase(podStatuses, GameServerPodPhase.Running)
419
420
  ) {
420
- console.log('Waiting for gameserver(s) to be ready...')
421
+ console.log('Waiting for pod(s) to be ready...')
421
422
  for (let ndx = 0; ndx < pods.length; ndx += 1) {
422
423
  const status = podStatuses[ndx]
423
424
  const suffix = status.phase !== GameServerPodPhase.Ready ? ` -- ${status.message}` : ''
424
425
  console.log(` ${pods[ndx].metadata?.name}: ${status.phase}${suffix}`)
425
426
  }
426
427
  } else if (allPodsInPhase(podStatuses, GameServerPodPhase.Ready)) {
427
- console.log('Gameserver is up and ready to serve!')
428
+ console.log('Game server pod(s) are up and ready to serve!')
428
429
  return
429
430
  } else {
430
431
  console.log('Deployment in inconsistent state, waiting...')
@@ -445,9 +446,34 @@ export async function waitForGameServerPodsToBeReady(
445
446
  }
446
447
  }
447
448
 
449
+ async function waitForDomainResolution(hostname: string): Promise<void> {
450
+ // Use 15min timeout -- DNS propagation can take a while
451
+ const timeoutAt = Date.now() + 15 * 60 * 1000
452
+
453
+ while (true) {
454
+ try {
455
+ const addresses = await dns.lookup(hostname)
456
+ console.log(`Successfully resolved domain ${hostname} to:`, addresses)
457
+ return
458
+ } catch (err) {
459
+ if (Date.now() > timeoutAt) {
460
+ throw new Error(`Could not resolve domain ${hostname} before timeout.`)
461
+ }
462
+
463
+ if (err instanceof Error && (err as NodeJS.ErrnoException).code === 'ENOTFOUND') {
464
+ console.log(`Waiting for domain name ${hostname} to propagate... This can take up to 15 minutes on the first deploy.`)
465
+ } else {
466
+ console.log(`Failed to resolve ${hostname}: ${String(err)}. Retrying...`)
467
+ }
468
+ await delay(5000) // use long timeout as it can take ~5min for DNS to propagate
469
+ }
470
+ }
471
+ }
472
+
448
473
  /**
449
474
  * Wait until we can establish a client-simulating connection to the target
450
- * game server, or a timeout happens.
475
+ * game server and perform the TLS handshake and wait for the initial bytes
476
+ * to be received from the server, or a timeout happens.
451
477
  * @param hostname Hostname of the target server to connect to.
452
478
  * @param port Port to use for the connections (usually 9339).
453
479
  */
@@ -455,47 +481,49 @@ async function waitForGameServerClientEndpointToBeReady(
455
481
  hostname: string,
456
482
  port: number
457
483
  ): Promise<void> {
458
- const checkConnection = async (): Promise<boolean> => {
459
- return await new Promise((resolve) => {
460
- const socket = new net.Socket()
461
- // Set a short timeout for each attempt
462
- // Note: This does not include the DNS resolve time which can take a minute or so.
463
- // \todo Consider adding a DNS resolve step before trying to connect.
464
- socket.setTimeout(2000)
465
-
466
- socket.on('connect', () => {
467
- socket.destroy() // Clean up after success
468
- resolve(true)
469
- })
470
-
471
- socket.on('error', () => {
472
- socket.destroy() // Clean up after error
473
- resolve(false)
474
- })
475
-
476
- socket.on('timeout', () => {
477
- socket.destroy() // Clean up after timeout
478
- resolve(false)
479
- })
480
-
481
- socket.connect(port, hostname)
482
- })
483
- }
484
-
485
- // Try for 2 min before giving up
486
- const timeoutAt = Date.now() + 2 * 60 * 1000
484
+ // Try for a few minutes before giving up
485
+ const timeoutAt = Date.now() + 5 * 60 * 1000
487
486
 
488
487
  while (Date.now() < timeoutAt) {
489
- const connected = await checkConnection()
490
- if (connected) {
491
- console.log(`Successfully connected to ${hostname}:${port}`)
488
+ try {
489
+ await new Promise<void>((resolve, reject) => {
490
+ const socket: tls.TLSSocket = tls.connect({ host: hostname, port }, () => {
491
+ if (socket.authorized) {
492
+ console.log('TLS handshake completed, waiting to receive data from the server...')
493
+ // \todo This is enough with direct connection to the server but with the upcoming
494
+ // client-traffic-proxy, we may need to exchange the proper handshake messages
495
+ socket.once('data', (bytes: Buffer) => {
496
+ const hexBytes = Array.from(bytes).map(byte => byte.toString(16).padStart(2, '0'))
497
+ console.log(`Received ${bytes.length} bytes from server: ${hexBytes.join(' ')}`)
498
+ socket.end()
499
+ resolve()
500
+ })
501
+ } else {
502
+ socket.end()
503
+ reject(new Error(`TLS handshake failed: ${String(socket.authorizationError)}`))
504
+ }
505
+ })
506
+
507
+ socket.on('error', reject)
508
+
509
+ socket.setTimeout(5000, () => {
510
+ socket.end()
511
+ reject(new Error('Timeout while waiting for data after handshake'))
512
+ })
513
+ })
514
+
515
+ // Success
516
+ console.log(`Successfully connected to the target environment ${hostname}:${port}`)
492
517
  return
518
+ } catch (error) {
519
+ console.log(`Attempt failed, retrying: ${String(error)}`)
493
520
  }
494
- console.log(`Retrying connection to ${hostname}:${port}...`)
495
- await new Promise((resolve) => setTimeout(resolve, 1000))
521
+
522
+ // Wait a bit before trying again
523
+ await delay(1000)
496
524
  }
497
525
 
498
- throw new Error(`Timeout while trying to connect to ${hostname}:${port}.`)
526
+ throw new Error(`Timeout reached while waiting to establish connection to ${hostname}:${port}`)
499
527
  }
500
528
 
501
529
  /**
@@ -515,7 +543,10 @@ export async function checkGameServerDeployment(
515
543
  console.log('Waiting for game server pods to be ready...')
516
544
  await waitForGameServerPodsToBeReady(envInfo.deployment.kubernetes_namespace, kubeconfig, requiredImageTag)
517
545
 
518
- // \todo Add a separate step for a DNS check on the game server
546
+ // Resolve DNS for the target server
547
+ // \todo Use the -proxy address when client-traffic-proxy is in use
548
+ console.log(`Resolving the domain name '${envInfo.deployment.server_hostname}' of the server...`)
549
+ await waitForDomainResolution(envInfo.deployment.server_hostname)
519
550
 
520
551
  // Check that the game server accepts traffic on its client-facing endpoint
521
552
  const clientTrafficPort = 9339 // \todo check for other ports, too?
@@ -78,7 +78,7 @@ export async function setSecret(key: string, value: any): Promise<void> {
78
78
  await fs.writeFile(filePath, content)
79
79
  }
80
80
 
81
- export async function getSecret(key: string): Promise<any | undefined> {
81
+ export async function getSecret(key: string): Promise<string> {
82
82
  logger.debug(`Getting secret ${key}...`)
83
83
 
84
84
  const secrets = await loadSecrets()
@@ -113,7 +113,7 @@ export class TargetEnvironment {
113
113
  }
114
114
 
115
115
  async fetchText(url: string, method: string): Promise<string> {
116
- // eslint-disable-next-line @typescript-eslint/init-declarations
116
+
117
117
  let response
118
118
  try {
119
119
  response = await fetch(url, {
@@ -168,7 +168,7 @@ export class TargetEnvironment {
168
168
  logger.debug(`Getting Kubernetes KubeConfig from ${url}...`)
169
169
 
170
170
  // get the execcredential and morph it into a kubeconfig which calls metaplay-auth for the token
171
- // eslint-disable-next-line @typescript-eslint/init-declarations
171
+
172
172
  let kubeExecCredential: KubeExecCredential
173
173
  try {
174
174
  kubeExecCredential = await this.fetchJson<KubeExecCredential>(url, 'POST')
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = "1.7.0"
1
+ export const PACKAGE_VERSION = "1.7.1"