@metaplay/metaplay-auth 1.2.0 → 1.3.0

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.
@@ -1,21 +1,21 @@
1
1
  import { isValidFQDN, getGameserverAdminUrl } from './utils.js';
2
2
  import { logger } from './logging.js';
3
+ import { dump } from 'js-yaml';
4
+ import { getUserinfo } from './auth.js';
3
5
  export class StackAPI {
4
6
  accessToken;
5
- _stack_api_base_uri;
6
- constructor(accessToken) {
7
+ _stackApiBaseUrl;
8
+ constructor(accessToken, stackApiBaseUrl) {
7
9
  if (accessToken == null) {
8
10
  throw new Error('accessToken must be provided');
9
11
  }
10
12
  this.accessToken = accessToken;
11
- this._stack_api_base_uri = 'https://infra.p1.metaplay.io/stackapi';
12
- }
13
- get stack_api_base_uri() {
14
- return this._stack_api_base_uri.replace(/\/$/, '');
15
- }
16
- set stack_api_base_uri(uri) {
17
- uri = uri.replace(/\/$/, ''); // Remove trailing slash
18
- this._stack_api_base_uri = uri;
13
+ if (stackApiBaseUrl) {
14
+ this._stackApiBaseUrl = stackApiBaseUrl.replace(/\/$/, ''); // Remove trailing slash
15
+ }
16
+ else {
17
+ this._stackApiBaseUrl = 'https://infra.p1.metaplay.io/stackapi';
18
+ }
19
19
  }
20
20
  async getAwsCredentials(gs) {
21
21
  let url = '';
@@ -25,11 +25,11 @@ export class StackAPI {
25
25
  url = `https://${adminUrl}/.infra/credentials/aws`;
26
26
  }
27
27
  else {
28
- url = `${this.stack_api_base_uri}/v0/credentials/${gs.gameserver}/aws`;
28
+ url = `${this._stackApiBaseUrl}/v0/credentials/${gs.gameserver}/aws`;
29
29
  }
30
30
  }
31
31
  else if (gs.organization != null && gs.project != null && gs.environment != null) {
32
- url = `${this.stack_api_base_uri}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/aws`;
32
+ url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/aws`;
33
33
  }
34
34
  else {
35
35
  throw new Error('Invalid arguments for getAwsCredentials');
@@ -55,11 +55,11 @@ export class StackAPI {
55
55
  url = `https://${adminUrl}/.infra/credentials/k8s`;
56
56
  }
57
57
  else {
58
- url = `${this.stack_api_base_uri}/v0/credentials/${gs.gameserver}/k8s`;
58
+ url = `${this._stackApiBaseUrl}/v0/credentials/${gs.gameserver}/k8s`;
59
59
  }
60
60
  }
61
61
  else if (gs.organization != null && gs.project != null && gs.environment != null) {
62
- url = `${this.stack_api_base_uri}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/k8s`;
62
+ url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/k8s`;
63
63
  }
64
64
  else {
65
65
  throw new Error('Invalid arguments for getKubeConfig');
@@ -77,6 +77,137 @@ export class StackAPI {
77
77
  }
78
78
  return await response.text();
79
79
  }
80
+ /**
81
+ * Get a `kubeconfig` payload which invokes `metaplay-auth get-kubernetes-execcredential` to get the actual
82
+ * access credentials each time the kubeconfig is used.
83
+ * @param gs Game server environment to get credentials for.
84
+ * @returns The kubeconfig YAML.
85
+ */
86
+ async getKubeConfigExecCredential(gs) {
87
+ let url = '';
88
+ let gsSlug = '';
89
+ const execArgs = ['get-kubernetes-execcredential'];
90
+ if (gs.gameserver != null) {
91
+ const adminUrl = getGameserverAdminUrl(gs.gameserver);
92
+ url = `https://${adminUrl}/.infra/credentials/k8s?type=execcredential`;
93
+ gsSlug = gs.gameserver;
94
+ execArgs.push('--gameserver', gs.gameserver);
95
+ }
96
+ else if (gs.organization != null && gs.project != null && gs.environment != null) {
97
+ url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/k8s?type=execcredential`;
98
+ gsSlug = `${gs.organization}.${gs.project}.${gs.environment}`;
99
+ execArgs.push('--organization', gs.organization, '--project', gs.project, '--environment', gs.environment);
100
+ }
101
+ else {
102
+ throw new Error('Invalid arguments for getKubeConfigExecCredential');
103
+ }
104
+ logger.debug(`Getting Kubernetes KubeConfig from ${url}...`);
105
+ const response = await fetch(url, {
106
+ method: 'POST',
107
+ headers: {
108
+ Authorization: `Bearer ${this.accessToken}`,
109
+ 'Content-Type': 'application/json'
110
+ }
111
+ });
112
+ if (response.status !== 200) {
113
+ throw new Error(`Failed to fetch Kubernetes KubeConfig: ${response.statusText}`);
114
+ }
115
+ // get the execcredential and morph it into a kubeconfig which calls metaplay-auth for the token
116
+ let kubeExecCredential;
117
+ try {
118
+ kubeExecCredential = await response.json();
119
+ }
120
+ catch {
121
+ throw new Error('Failed to fetch Kubernetes KubeConfig: the server response is not JSON');
122
+ }
123
+ if (!kubeExecCredential.spec.cluster) {
124
+ throw new Error('Received kubeExecCredential with missing spec.cluster');
125
+ }
126
+ let namespace = 'default';
127
+ let user = 'user';
128
+ try {
129
+ const environment = await this.getEnvironmentDetails(gs);
130
+ namespace = environment.deployment?.kubernetes_namespace ?? namespace;
131
+ }
132
+ catch (e) {
133
+ logger.debug('Failed to get environment details, using defaults', e);
134
+ }
135
+ try {
136
+ const userinfo = await getUserinfo(this.accessToken);
137
+ user = userinfo.sub ?? user;
138
+ }
139
+ catch (e) {
140
+ logger.debug('Failed to get userinfo, using defaults', e);
141
+ }
142
+ const kubeConfig = {
143
+ apiVersion: 'v1',
144
+ kind: 'Config',
145
+ 'current-context': gsSlug,
146
+ clusters: [
147
+ {
148
+ cluster: {
149
+ 'certificate-authority-data': kubeExecCredential.spec.cluster.certificateAuthorityData,
150
+ server: kubeExecCredential.spec.cluster.server
151
+ },
152
+ name: kubeExecCredential.spec.cluster.server
153
+ }
154
+ ],
155
+ contexts: [
156
+ {
157
+ context: {
158
+ cluster: kubeExecCredential.spec.cluster.server,
159
+ namespace,
160
+ user
161
+ },
162
+ name: gsSlug,
163
+ }
164
+ ],
165
+ users: [
166
+ {
167
+ name: user,
168
+ user: {
169
+ exec: {
170
+ apiVersion: 'client.authentication.k8s.io/v1beta1',
171
+ command: 'metaplay-auth',
172
+ args: execArgs,
173
+ }
174
+ }
175
+ }
176
+ ],
177
+ };
178
+ // return as yaml for easier consumption
179
+ return dump(kubeConfig);
180
+ }
181
+ async getKubeExecCredential(gs) {
182
+ let url = '';
183
+ if (gs.gameserver != null) {
184
+ if (isValidFQDN(gs.gameserver)) {
185
+ const adminUrl = getGameserverAdminUrl(gs.gameserver);
186
+ url = `https://${adminUrl}/.infra/credentials/k8s?type=execcredential`;
187
+ }
188
+ else {
189
+ url = `${this._stackApiBaseUrl}/v0/credentials/${gs.gameserver}/k8s?type=execcredential`;
190
+ }
191
+ }
192
+ else if (gs.organization != null && gs.project != null && gs.environment != null) {
193
+ url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/k8s?type=execcredential`;
194
+ }
195
+ else {
196
+ throw new Error('Invalid arguments for getKubeConfig');
197
+ }
198
+ logger.debug(`Getting Kubernetes ExecCredential from ${url}...`);
199
+ const response = await fetch(url, {
200
+ method: 'POST',
201
+ headers: {
202
+ Authorization: `Bearer ${this.accessToken}`,
203
+ 'Content-Type': 'application/json'
204
+ }
205
+ });
206
+ if (response.status !== 200) {
207
+ throw new Error(`Failed to fetch Kubernetes ExecCredential: ${response.statusText}`);
208
+ }
209
+ return await response.text();
210
+ }
80
211
  async getEnvironmentDetails(gs) {
81
212
  let url = '';
82
213
  if (gs.gameserver != null) {
@@ -85,11 +216,11 @@ export class StackAPI {
85
216
  url = `https://${adminUrl}/.infra/environment`;
86
217
  }
87
218
  else {
88
- url = `${this.stack_api_base_uri}/v0/deployments/${gs.gameserver}`;
219
+ url = `${this._stackApiBaseUrl}/v0/deployments/${gs.gameserver}`;
89
220
  }
90
221
  }
91
222
  else if (gs.organization != null && gs.project != null && gs.environment != null) {
92
- url = `${this.stack_api_base_uri}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}`;
223
+ url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}`;
93
224
  }
94
225
  else {
95
226
  throw new Error('Invalid arguments for environment details');
@@ -1 +1 @@
1
- {"version":3,"file":"stackapi.js","sourceRoot":"","sources":["../../src/stackapi.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stackapi.js","sourceRoot":"","sources":["../../src/stackapi.ts"],"names":[],"mappings":"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"}
package/index.ts CHANGED
@@ -2,17 +2,42 @@
2
2
  import { Command } from 'commander'
3
3
  import { loginAndSaveTokens, machineLoginAndSaveTokens, extendCurrentSession, loadTokens, removeTokens } from './src/auth.js'
4
4
  import { StackAPI } from './src/stackapi.js'
5
+ import type { GameserverId } from './src/stackapi.js'
5
6
  import { checkGameServerDeployment } from './src/deployment.js'
6
7
  import { logger, setLogLevel } from './src/logging.js'
8
+ import { isValidFQDN } from './src/utils.js'
7
9
  import { exit } from 'process'
8
10
  import { ECRClient, GetAuthorizationTokenCommand } from '@aws-sdk/client-ecr'
9
11
 
12
+ /**
13
+ * Helper for parsing the GameserverId type from the command line arguments. Accepts either the gameserver address or the
14
+ * (organization, project, environment) tuple from options.
15
+ */
16
+ function resolveGameserverId (address: string | undefined, options: any): GameserverId {
17
+ // If address is specified, use it, otherwise assume options has organization, project, and environment
18
+ if (address) {
19
+ if (isValidFQDN(address)) {
20
+ return { gameserver: address }
21
+ } else {
22
+ const parts = address.split('-')
23
+ if (parts.length !== 3) {
24
+ throw new Error('Invalid gameserver address syntax: specify either <organiation>-<project>-<environment> or a fully-qualified domain name (eg, idler-develop.p1.metaplay.io)')
25
+ }
26
+ return { organization: parts[0], project: parts[1], environment: parts[2] }
27
+ }
28
+ } else if (options.organization && options.project && options.environment) {
29
+ return { organization: options.organization, project: options.project, environment: options.environment }
30
+ } else {
31
+ throw new Error('Could not determine target environment from arguments: You need to specify either a gameserver address or an organization, project, and environment. Run this command with --help flag for more information.')
32
+ }
33
+ }
34
+
10
35
  const program = new Command()
11
36
 
12
37
  program
13
38
  .name('metaplay-auth')
14
39
  .description('Authenticate with Metaplay and get AWS and Kubernetes credentials for game servers.')
15
- .version('1.2.0')
40
+ .version('1.3.0')
16
41
  .option('-d, --debug', 'enable debug output')
17
42
  .hook('preAction', (thisCommand) => {
18
43
  // Handle debug flag for all commands.
@@ -94,43 +119,76 @@ program.command('show-tokens')
94
119
  })
95
120
 
96
121
  program.command('get-kubeconfig')
97
- .description('get kubeconfig for deployment')
98
- .argument('[gameserver]', 'address of gameserver (e.g. idler-develop.p1.metaplay.io)')
122
+ .description('get kubeconfig for target environment')
123
+ .argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
99
124
  .option('-s, --stack-api <stack-api-base-path>', 'explicit stack api (e.g. https://infra.p1.metaplay.io/stackapi/)')
100
125
  .option('-o, --organization <organization>', 'organization name (e.g. metaplay)')
101
126
  .option('-p, --project <project>', 'project name (e.g. idler)')
102
127
  .option('-e, --environment <environment>', 'environment name (e.g. develop)')
128
+ .option('-t, --type <format>', 'output format (static or dynamic)', 'static')
103
129
  .hook('preAction', async () => {
104
130
  await extendCurrentSession()
105
131
  })
106
132
  .action(async (gameserver, options) => {
107
133
  try {
108
134
  const tokens = await loadTokens()
109
-
110
- if (!gameserver && !(options.organization && options.project && options.environment)) {
111
- throw new Error('Could not determine a deployment to fetch the KubeConfigs from. You need to specify either a gameserver or an organization, project, and environment')
135
+ const stackApi = new StackAPI(tokens.access_token, options.stackApi)
136
+ const gameserverId = resolveGameserverId(gameserver, options)
137
+
138
+ if (options.type === 'dynamic') {
139
+ const credentials = await stackApi.getKubeConfigExecCredential(gameserverId)
140
+ console.log(credentials)
141
+ } else if (options.type === 'static') {
142
+ const credentials = await stackApi.getKubeConfig(gameserverId)
143
+ console.log(credentials)
144
+ } else {
145
+ throw new Error('Invalid type; must be one of static or dynamic')
112
146
  }
113
-
114
- const stackApi = new StackAPI(tokens.access_token)
115
- if (options.stackApi) {
116
- stackApi.stack_api_base_uri = options.stackApi
147
+ } catch (error) {
148
+ if (error instanceof Error) {
149
+ console.error('Error getting KubeConfig:', error)
117
150
  }
151
+ exit(1)
152
+ }
153
+ })
118
154
 
119
- const payload = gameserver ? { gameserver } : { organization: options.organization, project: options.project, environment: options.environment }
155
+ /**
156
+ * Get the Kubernetes credentials in the execcredential format which can be used within the `kubeconfig` file:
157
+ * The kubeconfig can invoke this command to fetch the Kubernetes credentials just-in-time which allows us to
158
+ * generate kubeconfig files that don't contain access tokens and are longer-lived (the authentication is the
159
+ * same as that of metaplay-auth itself). Use `metaplay-auth get-kubeconfig -t dynamic ...` to create a
160
+ * kubeconfig that uses this command.
161
+ */
162
+ // todo: maybe this should be a hidden command as it's not very useful for end users and clutters help?
163
+ program.command('get-kubernetes-execcredential')
164
+ .description('get kubernetes credentials in execcredential format (only intended to be used within a kubeconfig)')
165
+ .argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
166
+ .option('-s, --stack-api <stack-api-base-path>', 'explicit stack api (e.g. https://infra.p1.metaplay.io/stackapi/)')
167
+ .option('-o, --organization <organization>', 'organization name (e.g. metaplay)')
168
+ .option('-p, --project <project>', 'project name (e.g. idler)')
169
+ .option('-e, --environment <environment>', 'environment name (e.g. develop)')
170
+ .hook('preAction', async () => {
171
+ await extendCurrentSession()
172
+ })
173
+ .action(async (gameserver, options) => {
174
+ try {
175
+ const tokens = await loadTokens()
176
+ const stackApi = new StackAPI(tokens.access_token, options.stackApi)
177
+ const gameserverId = resolveGameserverId(gameserver, options)
120
178
 
121
- const credentials = await stackApi.getKubeConfig(payload)
179
+ const credentials = await stackApi.getKubeExecCredential(gameserverId)
122
180
  console.log(credentials)
123
181
  } catch (error) {
124
182
  if (error instanceof Error) {
125
- console.error(`Error getting KubeConfig: ${error.message}`)
183
+ console.error(`Error getting Kubernetes ExecCredential: ${error.message}`)
126
184
  }
127
185
  exit(1)
128
186
  }
129
187
  })
130
188
 
131
189
  program.command('get-aws-credentials')
132
- .description('get AWS credentials for deployment')
133
- .argument('[gameserver]', 'address of gameserver (e.g. idler-develop.p1.metaplay.io)')
190
+ .description('get AWS credentials for target environment')
191
+ .argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
134
192
  .option('-s, --stack-api <stack-api-base-path>', 'explicit stack api (e.g. https://infra.p1.metaplay.io/stackapi/)')
135
193
  .option('-o, --organization <organization>', 'organization name (e.g. metaplay)')
136
194
  .option('-p, --project <project>', 'project name (e.g. idler)')
@@ -146,19 +204,10 @@ program.command('get-aws-credentials')
146
204
  }
147
205
 
148
206
  const tokens = await loadTokens()
207
+ const stackApi = new StackAPI(tokens.access_token, options.stackApi)
208
+ const gameserverId = resolveGameserverId(gameserver, options)
149
209
 
150
- if (!gameserver && !(options.organization && options.project && options.environment)) {
151
- throw new Error('Could not determine a deployment to fetch the AWS credentials from. You need to specify either a gameserver or an organization, project, and environment')
152
- }
153
-
154
- const stackApi = new StackAPI(tokens.access_token)
155
- if (options.stackApi) {
156
- stackApi.stack_api_base_uri = options.stackApi
157
- }
158
-
159
- const payload = gameserver ? { gameserver } : { organization: options.organization, project: options.project, environment: options.environment }
160
-
161
- const credentials = await stackApi.getAwsCredentials(payload)
210
+ const credentials = await stackApi.getAwsCredentials(gameserverId)
162
211
 
163
212
  if (options.format === 'env') {
164
213
  console.log(`export AWS_ACCESS_KEY_ID=${credentials.AccessKeyId}`)
@@ -179,8 +228,8 @@ program.command('get-aws-credentials')
179
228
  })
180
229
 
181
230
  program.command('get-docker-login')
182
- .description('get docker login credentials for pushing the server image')
183
- .argument('[gameserver]', 'address of gameserver (e.g. idler-develop.p1.metaplay.io)')
231
+ .description('get docker login credentials for pushing the server image to target environment')
232
+ .argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
184
233
  .option('-o, --organization <organization>', 'organization name (e.g. metaplay)')
185
234
  .option('-p, --project <project>', 'project name (e.g. idler)')
186
235
  .option('-e, --environment <environment>', 'environment name (e.g. develop)')
@@ -195,24 +244,16 @@ program.command('get-docker-login')
195
244
  }
196
245
 
197
246
  const tokens = await loadTokens()
198
-
199
- if (!gameserver && !(options.organization && options.project && options.environment)) {
200
- throw new Error('Could not determine a game server deployment. You need to specify either a gameserver or an organization, project, and environment')
201
- }
202
-
203
- const stackApi = new StackAPI(tokens.access_token)
204
- if (options.stackApi) {
205
- stackApi.stack_api_base_uri = options.stackApi
206
- }
247
+ const stackApi = new StackAPI(tokens.access_token, options.stackApi)
248
+ const gameserverId = resolveGameserverId(gameserver, options)
207
249
 
208
250
  // Fetch AWS credentials from Metaplay cloud
209
251
  logger.debug('Get AWS credentials from Metaplay')
210
- const payload = gameserver ? { gameserver } : { organization: options.organization, project: options.project, environment: options.environment }
211
- const credentials = await stackApi.getAwsCredentials(payload)
252
+ const credentials = await stackApi.getAwsCredentials(gameserverId)
212
253
 
213
254
  // Get environment info (region is needed for ECR)
214
255
  logger.debug('Get environment info')
215
- const environment = await stackApi.getEnvironmentDetails(payload)
256
+ const environment = await stackApi.getEnvironmentDetails(gameserverId)
216
257
  const awsRegion = environment.deployment.aws_region
217
258
  const dockerRepo = environment.deployment.ecr_repo
218
259
 
@@ -262,8 +303,8 @@ program.command('get-docker-login')
262
303
  })
263
304
 
264
305
  program.command('get-environment')
265
- .description('get environment details for deployment')
266
- .argument('[gameserver]', 'address of gameserver (e.g. idler-develop.p1.metaplay.io)')
306
+ .description('get details of an environment')
307
+ .argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
267
308
  .option('-s, --stack-api <stack-api-base-path>', 'explicit stack api (e.g. https://infra.p1.metaplay.io/stackapi/)')
268
309
  .option('-o, --organization <organization>', 'organization name (e.g. metaplay)')
269
310
  .option('-p, --project <project>', 'project name (e.g. idler)')
@@ -274,19 +315,10 @@ program.command('get-environment')
274
315
  .action(async (gameserver, options) => {
275
316
  try {
276
317
  const tokens = await loadTokens()
318
+ const stackApi = new StackAPI(tokens.access_token, options.stackApi)
319
+ const gameserverId = resolveGameserverId(gameserver, options)
277
320
 
278
- if (!gameserver && !(options.organization && options.project && options.environment)) {
279
- throw new Error('Could not determine a deployment to fetch environment details from. You need to specify either a gameserver or an organization, project, and environment')
280
- }
281
-
282
- const stackApi = new StackAPI(tokens.access_token)
283
- if (options.stackApi) {
284
- stackApi.stack_api_base_uri = options.stackApi
285
- }
286
-
287
- const payload = gameserver ? { gameserver } : { organization: options.organization, project: options.project, environment: options.environment }
288
-
289
- const environment = await stackApi.getEnvironmentDetails(payload)
321
+ const environment = await stackApi.getEnvironmentDetails(gameserverId)
290
322
  console.log(JSON.stringify(environment))
291
323
  } catch (error) {
292
324
  if (error instanceof Error) {
@@ -310,8 +342,8 @@ program.command('check-deployment')
310
342
  // Run the checks and exit with success/failure exitCode depending on result
311
343
  const exitCode = await checkGameServerDeployment(namespace)
312
344
  exit(exitCode)
313
- } catch (error) {
314
- console.error(`Failed to check deployment status: ${error}`)
345
+ } catch (error: any) {
346
+ console.error(`Failed to check deployment status: ${error.message}`)
315
347
  exit(1)
316
348
  }
317
349
  })
package/package.json CHANGED
@@ -1,38 +1,40 @@
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.2.0",
4
+ "version": "1.3.0",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
7
7
  "homepage": "https://metaplay.io",
8
8
  "bin": "dist/index.js",
9
+ "scripts": {
10
+ "dev": "tsx index.ts",
11
+ "prepublish": "tsc"
12
+ },
9
13
  "publishConfig": {
10
14
  "access": "public"
11
15
  },
12
16
  "devDependencies": {
17
+ "@metaplay/eslint-config": "workspace:*",
18
+ "@metaplay/typescript-config": "workspace:*",
13
19
  "@types/express": "^4.17.21",
20
+ "@types/js-yaml": "^4.0.9",
14
21
  "@types/jsonwebtoken": "^9.0.5",
15
22
  "@types/jwk-to-pem": "^2.0.3",
16
- "@types/node": "^20.11.28",
23
+ "@types/node": "^20.12.5",
17
24
  "tsx": "^4.7.1",
18
25
  "typescript": "^5.1.6",
19
- "vitest": "^1.3.1",
20
- "@metaplay/eslint-config": "1.0.0",
21
- "@metaplay/typescript-config": "1.0.0"
26
+ "vitest": "^1.4.0"
22
27
  },
23
28
  "dependencies": {
24
- "@aws-sdk/client-ecr": "^3.535.0",
25
- "@kubernetes/client-node": "^0.20.0",
26
- "@ory/client": "^1.6.2",
29
+ "@aws-sdk/client-ecr": "^3.549.0",
30
+ "@kubernetes/client-node": "^1.0.0-rc4",
31
+ "@ory/client": "^1.9.0",
27
32
  "commander": "^12.0.0",
28
- "h3": "^1.10.2",
33
+ "h3": "^1.11.1",
34
+ "js-yaml": "^4.1.0",
29
35
  "jsonwebtoken": "^9.0.2",
30
36
  "jwk-to-pem": "^2.0.5",
31
- "open": "^10.0.2",
37
+ "open": "^10.1.0",
32
38
  "tslog": "^4.9.2"
33
- },
34
- "scripts": {
35
- "dev": "tsx index.ts",
36
- "prepublish": "tsc"
37
39
  }
38
40
  }
package/src/auth.ts CHANGED
@@ -7,7 +7,7 @@ import open from 'open'
7
7
  import { randomBytes, createHash } from 'node:crypto'
8
8
  import jwt from 'jsonwebtoken'
9
9
  import jwkToPem from 'jwk-to-pem'
10
- import { Configuration, WellknownApi } from '@ory/client'
10
+ import { Configuration, WellknownApi, OidcApi } from '@ory/client'
11
11
  import { setSecret, getSecret, removeSecret } from './secret_store.js'
12
12
 
13
13
  import { logger } from './logging.js'
@@ -20,6 +20,9 @@ const tokenEndpoint = `${baseURL}/oauth2/token`
20
20
  const wellknownApi = new WellknownApi(new Configuration({
21
21
  basePath: baseURL,
22
22
  }))
23
+ const oidcApi = new OidcApi(new Configuration({
24
+ basePath: baseURL,
25
+ }))
23
26
 
24
27
  /**
25
28
  * A helper function which generates a code verifier and challenge for exchaning code from Ory server.
@@ -31,6 +34,34 @@ function generateCodeVerifierAndChallenge (): { verifier: string, challenge: str
31
34
  return { verifier, challenge }
32
35
  }
33
36
 
37
+ /**
38
+ * A helper function which fetches the user's info from an OIDC userinfo endpoint for the given token.
39
+ * @param token The token to fetch the userinfo for.
40
+ * @returns An object containing the user's info.
41
+ */
42
+ export async function getUserinfo (token: string): Promise<any> {
43
+ logger.debug('Trying to find OIDC well-known endpoints...')
44
+ const oidcRes = await oidcApi.discoverOidcConfiguration()
45
+
46
+ const userinfoEndpoint = oidcRes.data?.userinfo_endpoint
47
+ if (!userinfoEndpoint) {
48
+ throw new Error('No userinfo endpoint found in OIDC configuration')
49
+ }
50
+ logger.debug(`Found userinfo endpoint: ${userinfoEndpoint}`)
51
+
52
+ const userinfoRes = await fetch(userinfoEndpoint, {
53
+ headers: {
54
+ Authorization: `Bearer ${token}`
55
+ }
56
+ })
57
+
58
+ if (userinfoRes.status < 200 || userinfoRes.status >= 300) {
59
+ throw new Error(`Failed to fetch userinfo: ${userinfoRes.status} ${userinfoRes.statusText}`)
60
+ }
61
+
62
+ return await userinfoRes.json()
63
+ }
64
+
34
65
  /**
35
66
  * A helper function which finds an local available port to listen on.
36
67
  * @returns A promise that resolves to an available port.
package/src/deployment.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { promises as fs, existsSync } from 'fs'
1
+ import { existsSync } from 'fs'
2
2
  import { KubeConfig, CoreV1Api, V1Pod } from '@kubernetes/client-node'
3
3
  import { logger } from './logging.js'
4
- import { error } from 'console'
4
+ import type { CoreV1ApiListNamespacedPodRequest, CoreV1ApiReadNamespacedPodLogRequest } from '@kubernetes/client-node'
5
5
 
6
6
  enum GameServerPodPhase {
7
7
  Pending = 'Pending', // Still being deployed
@@ -18,14 +18,17 @@ interface GameServerPodStatus {
18
18
 
19
19
  async function fetchGameServerPods (k8sApi: CoreV1Api, namespace: string) {
20
20
  // Define label selector for gameserver
21
- const labelSelector = 'app=metaplay-server'
21
+ const param: CoreV1ApiListNamespacedPodRequest = {
22
+ namespace,
23
+ labelSelector: 'app=metaplay-server'
24
+ }
22
25
 
23
26
  try {
24
27
  // Get gameserver pods in the namespace
25
- const response = await k8sApi.listNamespacedPod(namespace, undefined, undefined, undefined, undefined, labelSelector)
28
+ const response = await k8sApi.listNamespacedPod(param)
26
29
 
27
30
  // Return pod statuses
28
- return response.body.items
31
+ return response.items
29
32
  } catch (error) {
30
33
  // \todo Better error handling ..
31
34
  console.log('Failed to fetch pods from Kubernetes:', error)
@@ -168,13 +171,19 @@ async function fetchPodLogs (k8sApi: CoreV1Api, pod: V1Pod) {
168
171
  throw new Error('Unable to determine pod metadata')
169
172
  }
170
173
 
171
- const pretty = 'True'
172
- const previous = false
173
- const tailLines = 100
174
- const timestamps = true
174
+ const params: CoreV1ApiReadNamespacedPodLogRequest = {
175
+ name: podName,
176
+ namespace,
177
+ container: containerName,
178
+ pretty: 'true',
179
+ previous: false,
180
+ tailLines: 100,
181
+ timestamps: true
182
+ }
183
+
175
184
  try {
176
- const response = await k8sApi.readNamespacedPodLog(podName, namespace, containerName, undefined, undefined, undefined, pretty, previous, undefined, tailLines, timestamps)
177
- return response.body
185
+ const response: string = await k8sApi.readNamespacedPodLog(params)
186
+ return response
178
187
  } catch (error) {
179
188
  // \todo Better error handling ..
180
189
  console.log('Failed to fetch pod logs from Kubernetes:', error)
@@ -218,7 +227,12 @@ export async function checkGameServerDeployment (namespace: string): Promise<num
218
227
 
219
228
  // Create Kubernetes API instance (with default kubeconfig)
220
229
  const kc = new KubeConfig()
221
- kc.loadFromFile(kubeconfigPath)
230
+ // Loda kubeconfig from file and throw error if validation fails.
231
+ try {
232
+ kc.loadFromFile(kubeconfigPath)
233
+ } catch (error) {
234
+ throw new Error(`Failed to load or validate kubeconfig. ${error}`)
235
+ }
222
236
  const k8sApi = kc.makeApiClient(CoreV1Api)
223
237
 
224
238
  // Figure out when to stop