@metaplay/metaplay-auth 1.5.0 → 1.6.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.
- package/.prettierignore +2 -0
- package/CHANGELOG.md +58 -40
- package/dist/index.cjs +125 -125
- package/eslint.config.js +3 -0
- package/index.ts +717 -439
- package/package.json +18 -15
- package/prettier.config.js +3 -0
- package/src/auth.ts +115 -80
- package/src/buildCommand.ts +267 -0
- package/src/config.ts +12 -0
- package/src/deployment.ts +182 -59
- package/src/logging.ts +4 -4
- package/src/secret_store.ts +10 -7
- package/src/stackapi.ts +2 -33
- package/src/targetenvironment.ts +61 -57
- package/src/utils.ts +120 -29
- package/src/version.ts +1 -1
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.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"homepage": "https://metaplay.io",
|
|
@@ -17,28 +17,31 @@
|
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@metaplay/eslint-config": "workspace:*",
|
|
20
|
-
"@
|
|
20
|
+
"@metaplay/prettier-config": "workspace:^",
|
|
21
|
+
"@prettier/plugin-pug": "workspace:^",
|
|
22
|
+
"@types/dockerode": "^3.3.31",
|
|
21
23
|
"@types/express": "^4.17.21",
|
|
22
24
|
"@types/js-yaml": "^4.0.9",
|
|
23
|
-
"@types/jsonwebtoken": "^9.0.
|
|
25
|
+
"@types/jsonwebtoken": "^9.0.6",
|
|
24
26
|
"@types/jwk-to-pem": "^2.0.3",
|
|
25
|
-
"@types/node": "^20.
|
|
27
|
+
"@types/node": "^20.16.1",
|
|
26
28
|
"@types/semver": "^7.5.8",
|
|
27
|
-
"esbuild": "^0.23.
|
|
28
|
-
"tsx": "^4.
|
|
29
|
-
"typescript": "5.4
|
|
30
|
-
"vitest": "^
|
|
31
|
-
"@aws-sdk/client-ecr": "^3.
|
|
29
|
+
"esbuild": "^0.23.1",
|
|
30
|
+
"tsx": "^4.19.0",
|
|
31
|
+
"typescript": "5.5.4",
|
|
32
|
+
"vitest": "^2.0.5",
|
|
33
|
+
"@aws-sdk/client-ecr": "^3.637.0",
|
|
32
34
|
"@kubernetes/client-node": "^1.0.0-rc6",
|
|
33
|
-
"@ory/client": "^1.
|
|
34
|
-
"commander": "^12.
|
|
35
|
+
"@ory/client": "^1.14.5",
|
|
36
|
+
"commander": "^12.1.0",
|
|
35
37
|
"dockerode": "^4.0.2",
|
|
36
|
-
"h3": "^1.
|
|
38
|
+
"h3": "^1.12.0",
|
|
37
39
|
"js-yaml": "^4.1.0",
|
|
38
40
|
"jsonwebtoken": "^9.0.2",
|
|
39
|
-
"jwk-to-pem": "^2.0.
|
|
41
|
+
"jwk-to-pem": "^2.0.6",
|
|
40
42
|
"open": "^8.4.2",
|
|
41
|
-
"semver": "^7.6.
|
|
42
|
-
"tslog": "^4.9.
|
|
43
|
+
"semver": "^7.6.3",
|
|
44
|
+
"tslog": "^4.9.3",
|
|
45
|
+
"prettier": "3.3.3"
|
|
43
46
|
}
|
|
44
47
|
}
|
package/src/auth.ts
CHANGED
|
@@ -9,7 +9,7 @@ import jwt from 'jsonwebtoken'
|
|
|
9
9
|
import jwkToPem from 'jwk-to-pem'
|
|
10
10
|
import { Configuration, WellknownApi, OidcApi } from '@ory/client'
|
|
11
11
|
import { setSecret, getSecret, removeSecret } from './secret_store.js'
|
|
12
|
-
|
|
12
|
+
import { portalBaseUrl } from './config.js'
|
|
13
13
|
import { logger } from './logging.js'
|
|
14
14
|
|
|
15
15
|
export interface TokenSet {
|
|
@@ -23,18 +23,22 @@ const clientId = 'c16ea663-ced3-46c6-8f85-38c9681fe1f0'
|
|
|
23
23
|
const baseURL = 'https://auth.metaplay.dev'
|
|
24
24
|
const authorizationEndpoint = `${baseURL}/oauth2/auth`
|
|
25
25
|
const tokenEndpoint = `${baseURL}/oauth2/token`
|
|
26
|
-
const wellknownApi = new WellknownApi(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
const wellknownApi = new WellknownApi(
|
|
27
|
+
new Configuration({
|
|
28
|
+
basePath: baseURL,
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
const oidcApi = new OidcApi(
|
|
32
|
+
new Configuration({
|
|
33
|
+
basePath: baseURL,
|
|
34
|
+
})
|
|
35
|
+
)
|
|
32
36
|
|
|
33
37
|
/**
|
|
34
38
|
* A helper function which generates a code verifier and challenge for exchaning code from Ory server.
|
|
35
39
|
* @returns
|
|
36
40
|
*/
|
|
37
|
-
function generateCodeVerifierAndChallenge
|
|
41
|
+
function generateCodeVerifierAndChallenge(): { verifier: string; challenge: string } {
|
|
38
42
|
const verifier: string = randomBytes(32).toString('hex')
|
|
39
43
|
const challenge: string = createHash('sha256').update(verifier).digest('base64url')
|
|
40
44
|
return { verifier, challenge }
|
|
@@ -45,7 +49,7 @@ function generateCodeVerifierAndChallenge (): { verifier: string, challenge: str
|
|
|
45
49
|
* @param token The token to fetch the userinfo for.
|
|
46
50
|
* @returns An object containing the user's info.
|
|
47
51
|
*/
|
|
48
|
-
export async function getUserinfo
|
|
52
|
+
export async function getUserinfo(token: string): Promise<any> {
|
|
49
53
|
logger.debug('Trying to find OIDC well-known endpoints...')
|
|
50
54
|
const oidcRes = await oidcApi.discoverOidcConfiguration()
|
|
51
55
|
|
|
@@ -57,8 +61,8 @@ export async function getUserinfo (token: string): Promise<any> {
|
|
|
57
61
|
|
|
58
62
|
const userinfoRes = await fetch(userinfoEndpoint, {
|
|
59
63
|
headers: {
|
|
60
|
-
Authorization: `Bearer ${token}
|
|
61
|
-
}
|
|
64
|
+
Authorization: `Bearer ${token}`,
|
|
65
|
+
},
|
|
62
66
|
})
|
|
63
67
|
|
|
64
68
|
if (userinfoRes.status < 200 || userinfoRes.status >= 300) {
|
|
@@ -72,14 +76,14 @@ export async function getUserinfo (token: string): Promise<any> {
|
|
|
72
76
|
* A helper function which finds an local available port to listen on.
|
|
73
77
|
* @returns A promise that resolves to an available port.
|
|
74
78
|
*/
|
|
75
|
-
async function findAvailablePort
|
|
79
|
+
async function findAvailablePort(): Promise<number> {
|
|
76
80
|
return await new Promise((resolve, reject) => {
|
|
77
81
|
// Ports need to be in sync with oauth2 client callbacks.
|
|
78
82
|
const portsToCheck = [5000, 5001, 5002, 5003, 5004]
|
|
79
83
|
let index = 0
|
|
80
84
|
|
|
81
85
|
// Test ports by opening a server on them.
|
|
82
|
-
function tryNextPort
|
|
86
|
+
function tryNextPort(): void {
|
|
83
87
|
if (index >= portsToCheck.length) {
|
|
84
88
|
reject(new Error('Could not find an available port.'))
|
|
85
89
|
}
|
|
@@ -109,7 +113,7 @@ async function findAvailablePort (): Promise<number> {
|
|
|
109
113
|
/**
|
|
110
114
|
* Log in and save tokens to the local secret store.
|
|
111
115
|
*/
|
|
112
|
-
export async function loginAndSaveTokens
|
|
116
|
+
export async function loginAndSaveTokens(): Promise<void> {
|
|
113
117
|
// Find an available port to listen on.
|
|
114
118
|
const availablePort = await findAvailablePort()
|
|
115
119
|
|
|
@@ -119,52 +123,57 @@ export async function loginAndSaveTokens () {
|
|
|
119
123
|
const state = randomBytes(16).toString('hex')
|
|
120
124
|
|
|
121
125
|
// Create a /callback endpoint that exchanges the code for tokens.
|
|
122
|
-
app.use(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
error_description: errorDescription,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
126
|
+
app.use(
|
|
127
|
+
'/callback',
|
|
128
|
+
defineEventHandler(async (event) => {
|
|
129
|
+
// Read the query parameters.
|
|
130
|
+
const { error, error_description: errorDescription, code } = getQuery(event)
|
|
131
|
+
|
|
132
|
+
// Raise an error if the query parameters contain an error message.
|
|
133
|
+
if (error) {
|
|
134
|
+
console.error(
|
|
135
|
+
`Error logging in. Received the following error:\n\n${String(error)}: ${String(errorDescription)}`
|
|
136
|
+
)
|
|
137
|
+
sendError(event, new Error(`Authentication failed: ${String(error)}: ${String(errorDescription)}`))
|
|
138
|
+
server.close()
|
|
139
|
+
process.exit(1)
|
|
140
|
+
}
|
|
137
141
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
// Raise an error if the query parameters do not contain a code.
|
|
143
|
+
if (typeof code !== 'string') {
|
|
144
|
+
console.error('Error logging in. No code received.')
|
|
145
|
+
sendError(event, new Error('Authentication failed: No code received.'))
|
|
146
|
+
server.close()
|
|
147
|
+
process.exit(1)
|
|
148
|
+
}
|
|
145
149
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
// Exchange the code for tokens.
|
|
151
|
+
try {
|
|
152
|
+
logger.debug(`Received callback request with code ${code}. Preparing to exchange for tokens...`)
|
|
153
|
+
const tokens = await getTokensWithAuthorizationCode(state, redirectUri, verifier, code)
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
|
|
155
|
+
// Only save access_token, id_token, and refresh_token
|
|
156
|
+
await saveTokens({
|
|
157
|
+
access_token: tokens.access_token,
|
|
158
|
+
id_token: tokens.id_token,
|
|
159
|
+
refresh_token: tokens.refresh_token,
|
|
160
|
+
})
|
|
153
161
|
|
|
154
|
-
|
|
162
|
+
console.log('You are now logged in and can call the other commands.')
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
// TODO: Could return a pre-generated HTML page instead of text?
|
|
165
|
+
return 'Authentication successful! You can close this window.'
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error instanceof Error) {
|
|
168
|
+
console.error(`Error: ${error.message}`)
|
|
169
|
+
}
|
|
170
|
+
} finally {
|
|
171
|
+
server.close()
|
|
161
172
|
}
|
|
162
|
-
} finally {
|
|
163
|
-
server.close()
|
|
164
|
-
}
|
|
165
173
|
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
process.exit(0)
|
|
175
|
+
})
|
|
176
|
+
)
|
|
168
177
|
|
|
169
178
|
// Start the server.
|
|
170
179
|
// We use use H3 and adapt it to Node.js's http server.
|
|
@@ -173,12 +182,14 @@ export async function loginAndSaveTokens () {
|
|
|
173
182
|
logger.debug(`Listening on port ${availablePort} and waiting for callback...`)
|
|
174
183
|
|
|
175
184
|
// Open the browser to log in.
|
|
176
|
-
const authorizationUrl
|
|
177
|
-
console.log(
|
|
185
|
+
const authorizationUrl = `${authorizationEndpoint}?response_type=code&client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&code_challenge=${challenge}&code_challenge_method=S256&scope=${encodeURIComponent('openid offline_access')}&state=${encodeURIComponent(state)}`
|
|
186
|
+
console.log(
|
|
187
|
+
`Attempting to open a browser to log in. If a browser did not open up, you can copy-paste the following URL to authenticate:\n\n${authorizationUrl}\n`
|
|
188
|
+
)
|
|
178
189
|
void open(authorizationUrl)
|
|
179
190
|
}
|
|
180
191
|
|
|
181
|
-
export async function machineLoginAndSaveTokens
|
|
192
|
+
export async function machineLoginAndSaveTokens(clientId: string, clientSecret: string): Promise<void> {
|
|
182
193
|
// Get a fresh access token from Metaplay Auth.
|
|
183
194
|
const params = new URLSearchParams()
|
|
184
195
|
params.set('grant_type', 'client_credentials')
|
|
@@ -195,27 +206,29 @@ export async function machineLoginAndSaveTokens (clientId: string, clientSecret:
|
|
|
195
206
|
})
|
|
196
207
|
|
|
197
208
|
// Return type checked manually by Teemu on 2024-3-7.
|
|
198
|
-
const tokens = await res.json() as { access_token: string
|
|
209
|
+
const tokens = (await res.json()) as { access_token: string; token_type: string; expires_in: number; scope: string }
|
|
199
210
|
|
|
200
211
|
logger.debug('Received machine authentication tokens, saving them for future use...')
|
|
201
212
|
|
|
202
213
|
await saveTokens({ access_token: tokens.access_token })
|
|
203
214
|
|
|
204
|
-
const userInfoResponse = await fetch(
|
|
215
|
+
const userInfoResponse = await fetch(`${portalBaseUrl}/api/external/userinfo`, {
|
|
205
216
|
headers: {
|
|
206
|
-
Authorization: `Bearer ${tokens.access_token}
|
|
217
|
+
Authorization: `Bearer ${tokens.access_token}`,
|
|
207
218
|
},
|
|
208
219
|
})
|
|
209
220
|
|
|
210
|
-
const userInfo = await userInfoResponse.json() as { given_name: string
|
|
221
|
+
const userInfo = (await userInfoResponse.json()) as { given_name: string; family_name: string }
|
|
211
222
|
|
|
212
|
-
console.log(
|
|
223
|
+
console.log(
|
|
224
|
+
`You are now logged in with machine user ${userInfo.given_name} ${userInfo.family_name} (clientId=${clientId}) and can execute the other commands.`
|
|
225
|
+
)
|
|
213
226
|
}
|
|
214
227
|
|
|
215
228
|
/**
|
|
216
229
|
* Refresh access and ID token with a refresh token.
|
|
217
230
|
*/
|
|
218
|
-
export async function extendCurrentSession
|
|
231
|
+
export async function extendCurrentSession(): Promise<void> {
|
|
219
232
|
try {
|
|
220
233
|
const tokens = await loadTokens()
|
|
221
234
|
|
|
@@ -228,13 +241,19 @@ export async function extendCurrentSession (): Promise<void> {
|
|
|
228
241
|
|
|
229
242
|
// Check that the refresh_token exists (machine users don't have it)
|
|
230
243
|
if (!tokens.refresh_token) {
|
|
231
|
-
throw new Error(
|
|
244
|
+
throw new Error(
|
|
245
|
+
'Cannot refresh an access_token without a refresh_token. With machine users, should just login again instead.'
|
|
246
|
+
)
|
|
232
247
|
}
|
|
233
248
|
|
|
234
249
|
logger.debug('Access token is no longer valid, trying to extend the current session with a refresh token.')
|
|
235
250
|
const refreshedTokens = await extendCurrentSessionWithRefreshToken(tokens.refresh_token)
|
|
236
251
|
|
|
237
|
-
await saveTokens({
|
|
252
|
+
await saveTokens({
|
|
253
|
+
access_token: refreshedTokens.access_token,
|
|
254
|
+
id_token: refreshedTokens.id_token,
|
|
255
|
+
refresh_token: refreshedTokens.refresh_token,
|
|
256
|
+
})
|
|
238
257
|
} catch (error) {
|
|
239
258
|
if (error instanceof Error) {
|
|
240
259
|
console.error(error.message)
|
|
@@ -248,13 +267,15 @@ export async function extendCurrentSession (): Promise<void> {
|
|
|
248
267
|
* @param refreshToken: The refresh token to use to get a new set of tokens.
|
|
249
268
|
* @returns A promise that resolves to a new set of tokens.
|
|
250
269
|
*/
|
|
251
|
-
async function extendCurrentSessionWithRefreshToken
|
|
270
|
+
async function extendCurrentSessionWithRefreshToken(
|
|
271
|
+
refreshToken: string
|
|
272
|
+
): Promise<{ id_token: string; access_token: string; refresh_token: string }> {
|
|
252
273
|
// TODO: similar to the todo task in getTokensWithAuthorizationCode, http request can be handled by ory/client.
|
|
253
274
|
const params = new URLSearchParams({
|
|
254
275
|
grant_type: 'refresh_token',
|
|
255
276
|
refresh_token: refreshToken,
|
|
256
277
|
scope: 'openid offline_access',
|
|
257
|
-
client_id: clientId
|
|
278
|
+
client_id: clientId,
|
|
258
279
|
})
|
|
259
280
|
|
|
260
281
|
logger.debug('Refreshing tokens...')
|
|
@@ -274,14 +295,16 @@ async function extendCurrentSessionWithRefreshToken (refreshToken: string): Prom
|
|
|
274
295
|
logger.error(`Failed to refresh tokens via endpoint ${tokenEndpoint}`)
|
|
275
296
|
logger.error('Fetch error details:', error)
|
|
276
297
|
if (error.cause?.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
|
277
|
-
throw new Error(
|
|
298
|
+
throw new Error(
|
|
299
|
+
`Failed to refresh tokens: SSL certificate validation failed for ${tokenEndpoint}. Is someone trying to tamper with your internet connection?`
|
|
300
|
+
)
|
|
278
301
|
}
|
|
279
302
|
throw new Error(`Failed to refresh tokens via ${tokenEndpoint}: ${error}`)
|
|
280
303
|
}
|
|
281
304
|
|
|
282
305
|
// Check if the response is OK
|
|
283
306
|
if (!response.ok) {
|
|
284
|
-
const responseJSON = await response.json() as { error: string
|
|
307
|
+
const responseJSON = (await response.json()) as { error: string; error_description: string }
|
|
285
308
|
|
|
286
309
|
logger.error('Failed to refresh tokens.')
|
|
287
310
|
logger.error(`Error Type: ${responseJSON.error}`)
|
|
@@ -294,7 +317,7 @@ async function extendCurrentSessionWithRefreshToken (refreshToken: string): Prom
|
|
|
294
317
|
throw new Error('Failed extending current session, exiting. Please log in again.')
|
|
295
318
|
}
|
|
296
319
|
|
|
297
|
-
return await response.json() as { id_token: string
|
|
320
|
+
return (await response.json()) as { id_token: string; access_token: string; refresh_token: string }
|
|
298
321
|
}
|
|
299
322
|
|
|
300
323
|
/**
|
|
@@ -305,22 +328,29 @@ async function extendCurrentSessionWithRefreshToken (refreshToken: string): Prom
|
|
|
305
328
|
* @param code
|
|
306
329
|
* @returns
|
|
307
330
|
*/
|
|
308
|
-
|
|
331
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
332
|
+
async function getTokensWithAuthorizationCode(
|
|
333
|
+
state: string,
|
|
334
|
+
redirectUri: string,
|
|
335
|
+
verifier: string,
|
|
336
|
+
code: string
|
|
337
|
+
): Promise<{ id_token: string; access_token: string; refresh_token: string }> {
|
|
309
338
|
// TODO: the authorication code exchange flow might be better to be handled by ory/client, could check if there's any useful toosl there.
|
|
310
339
|
try {
|
|
311
340
|
const response = await fetch(tokenEndpoint, {
|
|
312
341
|
method: 'POST',
|
|
313
342
|
headers: {
|
|
314
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
343
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
315
344
|
},
|
|
316
|
-
body: `grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(redirectUri)}&client_id=${clientId}&code_verifier=${verifier}&state=${encodeURIComponent(state)}
|
|
345
|
+
body: `grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(redirectUri)}&client_id=${clientId}&code_verifier=${verifier}&state=${encodeURIComponent(state)}`,
|
|
317
346
|
})
|
|
318
347
|
|
|
319
|
-
return await response.json() as { id_token: string
|
|
348
|
+
return (await response.json()) as { id_token: string; access_token: string; refresh_token: string }
|
|
320
349
|
} catch (error) {
|
|
321
350
|
if (error instanceof Error) {
|
|
322
351
|
logger.error(`Error exchanging code for tokens: ${error.message}`)
|
|
323
352
|
}
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
324
354
|
throw error
|
|
325
355
|
}
|
|
326
356
|
}
|
|
@@ -328,9 +358,9 @@ async function getTokensWithAuthorizationCode (state: string, redirectUri: strin
|
|
|
328
358
|
/**
|
|
329
359
|
* Load tokens from the local secret store.
|
|
330
360
|
*/
|
|
331
|
-
export async function loadTokens
|
|
361
|
+
export async function loadTokens(): Promise<TokenSet> {
|
|
332
362
|
try {
|
|
333
|
-
const tokens = await getSecret('tokens') as TokenSet
|
|
363
|
+
const tokens = (await getSecret('tokens')) as TokenSet
|
|
334
364
|
|
|
335
365
|
if (!tokens) {
|
|
336
366
|
throw new Error('Unable to load tokens. You need to login first.')
|
|
@@ -341,6 +371,7 @@ export async function loadTokens (): Promise<TokenSet> {
|
|
|
341
371
|
if (error instanceof Error) {
|
|
342
372
|
throw new Error(`Error loading tokens: ${error.message}`)
|
|
343
373
|
}
|
|
374
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
344
375
|
throw error
|
|
345
376
|
}
|
|
346
377
|
}
|
|
@@ -349,13 +380,15 @@ export async function loadTokens (): Promise<TokenSet> {
|
|
|
349
380
|
* Save tokens to the local secret store.
|
|
350
381
|
* @param tokens The tokens to save.
|
|
351
382
|
*/
|
|
352
|
-
export async function saveTokens
|
|
383
|
+
export async function saveTokens(tokens: Record<string, string>): Promise<void> {
|
|
353
384
|
try {
|
|
354
385
|
logger.debug('Received new tokens, verifying...')
|
|
355
386
|
|
|
356
387
|
// All tokens must have an access_token (machine users only have it)
|
|
357
388
|
if (!tokens.access_token) {
|
|
358
|
-
throw new Error(
|
|
389
|
+
throw new Error(
|
|
390
|
+
'Metaplay token has no access_token. Please log in again and make sure all checkboxes of permissions are selected before proceeding.'
|
|
391
|
+
)
|
|
359
392
|
}
|
|
360
393
|
|
|
361
394
|
logger.debug('Token verification completed, storing tokens...')
|
|
@@ -369,6 +402,7 @@ export async function saveTokens (tokens: Record<string, string>): Promise<void>
|
|
|
369
402
|
if (error instanceof Error) {
|
|
370
403
|
throw new Error(`Failed to save tokens: ${error.message}`)
|
|
371
404
|
}
|
|
405
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
372
406
|
throw error
|
|
373
407
|
}
|
|
374
408
|
}
|
|
@@ -376,7 +410,7 @@ export async function saveTokens (tokens: Record<string, string>): Promise<void>
|
|
|
376
410
|
/**
|
|
377
411
|
* Remove tokens from the local secret store.
|
|
378
412
|
*/
|
|
379
|
-
export async function removeTokens
|
|
413
|
+
export async function removeTokens(): Promise<void> {
|
|
380
414
|
try {
|
|
381
415
|
await removeSecret('tokens')
|
|
382
416
|
logger.debug('Removed tokens.')
|
|
@@ -384,6 +418,7 @@ export async function removeTokens (): Promise<void> {
|
|
|
384
418
|
if (error instanceof Error) {
|
|
385
419
|
throw new Error(`Error removing tokens: ${error.message}`)
|
|
386
420
|
}
|
|
421
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
387
422
|
throw error
|
|
388
423
|
}
|
|
389
424
|
}
|
|
@@ -393,7 +428,7 @@ export async function removeTokens (): Promise<void> {
|
|
|
393
428
|
* @param token The token being validated
|
|
394
429
|
* @returns return if token is valid, throw errors otherwise
|
|
395
430
|
*/
|
|
396
|
-
async function validateToken
|
|
431
|
+
async function validateToken(token: string): Promise<boolean> {
|
|
397
432
|
try {
|
|
398
433
|
// Decode the token
|
|
399
434
|
const completeTokenData = jwt.decode(token, { complete: true })
|
|
@@ -428,7 +463,7 @@ async function validateToken (token: string): Promise<boolean> {
|
|
|
428
463
|
* A helper function which shows token info after being fully decoded.
|
|
429
464
|
* @param token The token to show info for.
|
|
430
465
|
*/
|
|
431
|
-
async function showTokenInfo
|
|
466
|
+
async function showTokenInfo(token: string): Promise<void> {
|
|
432
467
|
logger.debug('Showing access token info...')
|
|
433
468
|
// Decode the token
|
|
434
469
|
const completeTokenData = jwt.decode(token, { complete: true })
|