@vercel/cli-auth 0.0.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/oauth.js ADDED
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.OAuth = OAuth;
37
+ exports.isOAuthError = isOAuthError;
38
+ const z = __importStar(require("zod/mini"));
39
+ const AuthorizationServerMetadata = z.object({
40
+ issuer: z.url(),
41
+ device_authorization_endpoint: z.url(),
42
+ token_endpoint: z.url(),
43
+ revocation_endpoint: z.url(),
44
+ jwks_uri: z.url(),
45
+ introspection_endpoint: z.url(),
46
+ });
47
+ const DeviceAuthorization = z.object({
48
+ device_code: z.string(),
49
+ user_code: z.string(),
50
+ verification_uri: z.url(),
51
+ verification_uri_complete: z.url(),
52
+ expires_in: z.number(),
53
+ interval: z.number(),
54
+ });
55
+ const IntrospectionResponse = z.object({
56
+ active: z.boolean(),
57
+ client_id: z.optional(z.string()),
58
+ session_id: z.optional(z.string()),
59
+ });
60
+ function OAuth(config) {
61
+ const { issuer, clientId, userAgent } = config;
62
+ let as;
63
+ function assertAs(as) {
64
+ if (as)
65
+ return;
66
+ throw new Error('Authorization Server Metadata not initialized. Call init() first.');
67
+ }
68
+ return {
69
+ async init() {
70
+ as ??= await this.authorizationServerMetadata();
71
+ return { ...this, as };
72
+ },
73
+ /**
74
+ * Returns the Authorization Server Metadata
75
+ *
76
+ * @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
77
+ * @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
78
+ */
79
+ async authorizationServerMetadata() {
80
+ const response = await fetch(new URL('.well-known/openid-configuration', issuer), {
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ 'user-agent': userAgent,
84
+ },
85
+ });
86
+ const json = await response.json();
87
+ as = AuthorizationServerMetadata.parse(json);
88
+ if (as.issuer !== issuer.origin) {
89
+ throw new OAuthError(`Issuer mismatch: expected ${issuer}, got ${as.issuer}`, json);
90
+ }
91
+ return as;
92
+ },
93
+ /**
94
+ * Perform the Device Authorization Request
95
+ *
96
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
97
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
98
+ */
99
+ async deviceAuthorizationRequest() {
100
+ assertAs(as);
101
+ const response = await fetch(as.device_authorization_endpoint, {
102
+ method: 'POST',
103
+ headers: {
104
+ 'Content-Type': 'application/x-www-form-urlencoded',
105
+ 'user-agent': userAgent,
106
+ },
107
+ body: new URLSearchParams({
108
+ client_id: clientId,
109
+ scope: 'openid offline_access',
110
+ }),
111
+ });
112
+ const json = await response.json();
113
+ const parsed = DeviceAuthorization.safeParse(json);
114
+ if (!parsed.success) {
115
+ throw new OAuthError(`Failed to parse device authorization response: ${parsed.error.message}`, json);
116
+ }
117
+ return {
118
+ device_code: parsed.data.device_code,
119
+ user_code: parsed.data.user_code,
120
+ verification_uri: parsed.data.verification_uri,
121
+ verification_uri_complete: parsed.data.verification_uri_complete,
122
+ expiresAt: Date.now() + parsed.data.expires_in * 1000,
123
+ interval: parsed.data.interval,
124
+ };
125
+ },
126
+ /**
127
+ * Perform the Device Access Token Request
128
+ *
129
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
130
+ */
131
+ async deviceAccessTokenRequest(device_code) {
132
+ assertAs(as);
133
+ try {
134
+ return [
135
+ null,
136
+ await fetch(as.token_endpoint, {
137
+ method: 'POST',
138
+ headers: {
139
+ 'Content-Type': 'application/x-www-form-urlencoded',
140
+ 'user-agent': userAgent,
141
+ },
142
+ body: new URLSearchParams({
143
+ client_id: clientId,
144
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
145
+ device_code,
146
+ }),
147
+ signal: AbortSignal.timeout(10 * 1000),
148
+ }),
149
+ ];
150
+ }
151
+ catch (error) {
152
+ if (error instanceof Error)
153
+ return [error];
154
+ return [
155
+ new Error('An unknown error occurred. See the logs for details.', {
156
+ cause: error,
157
+ }),
158
+ ];
159
+ }
160
+ },
161
+ /**
162
+ * Process the Token request Response
163
+ *
164
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
165
+ */
166
+ async processTokenResponse(response) {
167
+ const json = await response.json();
168
+ const processed = TokenSet.safeParse(json);
169
+ if (!processed.success) {
170
+ return [
171
+ new OAuthError(`Failed to parse token response: ${processed.error.message}`, json),
172
+ ];
173
+ }
174
+ return [null, processed.data];
175
+ },
176
+ /**
177
+ * Perform a Token Revocation Request.
178
+ *
179
+ * @see https://datatracker.ietf.org/doc/html/rfc7009#section-2.1
180
+ * @see https://datatracker.ietf.org/doc/html/rfc7009#section-2.2
181
+ */
182
+ async revokeToken(token) {
183
+ assertAs(as);
184
+ const response = await fetch(as.revocation_endpoint, {
185
+ method: 'POST',
186
+ headers: {
187
+ 'Content-Type': 'application/x-www-form-urlencoded',
188
+ 'user-agent': userAgent,
189
+ },
190
+ body: new URLSearchParams({ token, client_id: clientId }),
191
+ });
192
+ if (response.ok)
193
+ return;
194
+ const json = await response.json();
195
+ return new OAuthError('Revocation request failed', json);
196
+ },
197
+ /**
198
+ * Perform Refresh Token Request.
199
+ *
200
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-6
201
+ */
202
+ async refreshToken(token) {
203
+ assertAs(as);
204
+ const response = await fetch(as.token_endpoint, {
205
+ method: 'POST',
206
+ headers: {
207
+ 'Content-Type': 'application/x-www-form-urlencoded',
208
+ 'user-agent': userAgent,
209
+ },
210
+ body: new URLSearchParams({
211
+ client_id: clientId,
212
+ grant_type: 'refresh_token',
213
+ refresh_token: token,
214
+ }),
215
+ });
216
+ const [tokensError, tokenSet] = await this.processTokenResponse(response);
217
+ if (tokensError)
218
+ throw tokensError;
219
+ return tokenSet;
220
+ },
221
+ /**
222
+ * Perform Token Introspection Request.
223
+ *
224
+ * @see https://datatracker.ietf.org/doc/html/rfc7662#section-2.1
225
+ */
226
+ async introspectToken(token) {
227
+ assertAs(as);
228
+ const response = await fetch(as.introspection_endpoint, {
229
+ method: 'POST',
230
+ headers: {
231
+ 'Content-Type': 'application/x-www-form-urlencoded',
232
+ 'user-agent': userAgent,
233
+ },
234
+ body: new URLSearchParams({ token }),
235
+ });
236
+ const json = await response.json();
237
+ const processed = IntrospectionResponse.safeParse(json);
238
+ if (!processed.success) {
239
+ throw new OAuthError(`Failed to parse introspection response: ${processed.error.message}`, json);
240
+ }
241
+ return processed.data;
242
+ },
243
+ };
244
+ }
245
+ const TokenSet = z.object({
246
+ /** The access token issued by the authorization server. */
247
+ access_token: z.string(),
248
+ /** The type of the token issued */
249
+ token_type: z.literal('Bearer'),
250
+ /** The lifetime in seconds of the access token. */
251
+ expires_in: z.number(),
252
+ /** The refresh token, which can be used to obtain new access tokens. */
253
+ refresh_token: z.optional(z.string()),
254
+ /** The scope of the access token. */
255
+ scope: z.optional(z.string()),
256
+ });
257
+ const OAuthErrorResponse = z.object({
258
+ error: z.enum([
259
+ 'invalid_request',
260
+ 'invalid_client',
261
+ 'invalid_grant',
262
+ 'unauthorized_client',
263
+ 'unsupported_grant_type',
264
+ 'invalid_scope',
265
+ 'server_error',
266
+ // Device Authorization Response Errors
267
+ 'authorization_pending',
268
+ 'slow_down',
269
+ 'access_denied',
270
+ 'expired_token',
271
+ // Revocation Response Errors
272
+ 'unsupported_token_type',
273
+ ]),
274
+ error_description: z.optional(z.string()),
275
+ error_uri: z.optional(z.string()),
276
+ });
277
+ function processOAuthErrorResponse(json) {
278
+ try {
279
+ return OAuthErrorResponse.parse(json);
280
+ }
281
+ catch (error) {
282
+ if (error instanceof z.core.$ZodError) {
283
+ return new TypeError(`Invalid OAuth error response: ${error.message}`);
284
+ }
285
+ return new TypeError('Failed to parse OAuth error response');
286
+ }
287
+ }
288
+ class OAuthError extends Error {
289
+ constructor(message, response) {
290
+ super(message);
291
+ this.name = 'OAuthError';
292
+ const error = processOAuthErrorResponse(response);
293
+ if (error instanceof TypeError) {
294
+ const message = `Unexpected server response: ${JSON.stringify(response)}`;
295
+ this.cause = new Error(message, { cause: error });
296
+ this.code = 'server_error';
297
+ return;
298
+ }
299
+ let cause = error.error;
300
+ if (error.error_description)
301
+ cause += `: ${error.error_description}`;
302
+ if (error.error_uri)
303
+ cause += ` (${error.error_uri})`;
304
+ this.cause = new Error(cause.toString());
305
+ this.code = error.error;
306
+ }
307
+ }
308
+ function isOAuthError(error) {
309
+ return error instanceof OAuthError;
310
+ }
311
+ //# sourceMappingURL=oauth.js.map
package/oauth.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["oauth.ts"],"names":[],"mappings":"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"}
package/oauth.ts ADDED
@@ -0,0 +1,353 @@
1
+ import * as z from 'zod/mini';
2
+
3
+ const AuthorizationServerMetadata = z.object({
4
+ issuer: z.url(),
5
+ device_authorization_endpoint: z.url(),
6
+ token_endpoint: z.url(),
7
+ revocation_endpoint: z.url(),
8
+ jwks_uri: z.url(),
9
+ introspection_endpoint: z.url(),
10
+ });
11
+
12
+ export type AuthorizationServerMetadata = z.infer<
13
+ typeof AuthorizationServerMetadata
14
+ >;
15
+
16
+ const DeviceAuthorization = z.object({
17
+ device_code: z.string(),
18
+ user_code: z.string(),
19
+ verification_uri: z.url(),
20
+ verification_uri_complete: z.url(),
21
+ expires_in: z.number(),
22
+ interval: z.number(),
23
+ });
24
+
25
+ const IntrospectionResponse = z.object({
26
+ active: z.boolean(),
27
+ client_id: z.optional(z.string()),
28
+ session_id: z.optional(z.string()),
29
+ });
30
+
31
+ export function OAuth(config: {
32
+ issuer: URL;
33
+ clientId: string;
34
+ userAgent: string;
35
+ }) {
36
+ const { issuer, clientId, userAgent } = config;
37
+ let as: AuthorizationServerMetadata | undefined;
38
+
39
+ function assertAs(as: unknown): asserts as is AuthorizationServerMetadata {
40
+ if (as) return;
41
+ throw new Error(
42
+ 'Authorization Server Metadata not initialized. Call init() first.'
43
+ );
44
+ }
45
+
46
+ return {
47
+ async init() {
48
+ as ??= await this.authorizationServerMetadata();
49
+ return { ...this, as };
50
+ },
51
+ /**
52
+ * Returns the Authorization Server Metadata
53
+ *
54
+ * @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
55
+ * @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
56
+ */
57
+ async authorizationServerMetadata(): Promise<AuthorizationServerMetadata> {
58
+ const response = await fetch(
59
+ new URL('.well-known/openid-configuration', issuer),
60
+ {
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ 'user-agent': userAgent,
64
+ },
65
+ }
66
+ );
67
+
68
+ const json = await response.json();
69
+ as = AuthorizationServerMetadata.parse(json);
70
+
71
+ if (as.issuer !== issuer.origin) {
72
+ throw new OAuthError(
73
+ `Issuer mismatch: expected ${issuer}, got ${as.issuer}`,
74
+ json
75
+ );
76
+ }
77
+
78
+ return as;
79
+ },
80
+ /**
81
+ * Perform the Device Authorization Request
82
+ *
83
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
84
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
85
+ */
86
+ async deviceAuthorizationRequest(): Promise<{
87
+ /** The device verification code. */
88
+ device_code: string;
89
+ /** The end-user verification code. */
90
+ user_code: string;
91
+ /**
92
+ * The minimum amount of time in seconds that the client
93
+ * SHOULD wait between polling requests to the token endpoint.
94
+ */
95
+ interval: number;
96
+ /** The end-user verification URI on the authorization server. */
97
+ verification_uri: string;
98
+ /**
99
+ * The end-user verification URI on the authorization server,
100
+ * including the `user_code`, without redirection.
101
+ */
102
+ verification_uri_complete: string;
103
+ /**
104
+ * The absolute lifetime of the `device_code` and `user_code`.
105
+ * Calculated from `expires_in`.
106
+ */
107
+ expiresAt: number;
108
+ }> {
109
+ assertAs(as);
110
+ const response = await fetch(as.device_authorization_endpoint, {
111
+ method: 'POST',
112
+ headers: {
113
+ 'Content-Type': 'application/x-www-form-urlencoded',
114
+ 'user-agent': userAgent,
115
+ },
116
+ body: new URLSearchParams({
117
+ client_id: clientId,
118
+ scope: 'openid offline_access',
119
+ }),
120
+ });
121
+
122
+ const json = await response.json();
123
+ const parsed = DeviceAuthorization.safeParse(json);
124
+
125
+ if (!parsed.success) {
126
+ throw new OAuthError(
127
+ `Failed to parse device authorization response: ${parsed.error.message}`,
128
+ json
129
+ );
130
+ }
131
+
132
+ return {
133
+ device_code: parsed.data.device_code,
134
+ user_code: parsed.data.user_code,
135
+ verification_uri: parsed.data.verification_uri,
136
+ verification_uri_complete: parsed.data.verification_uri_complete,
137
+ expiresAt: Date.now() + parsed.data.expires_in * 1000,
138
+ interval: parsed.data.interval,
139
+ };
140
+ },
141
+ /**
142
+ * Perform the Device Access Token Request
143
+ *
144
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
145
+ */
146
+ async deviceAccessTokenRequest(
147
+ device_code: string
148
+ ): Promise<[Error] | [null, Response]> {
149
+ assertAs(as);
150
+ try {
151
+ return [
152
+ null,
153
+ await fetch(as.token_endpoint, {
154
+ method: 'POST',
155
+ headers: {
156
+ 'Content-Type': 'application/x-www-form-urlencoded',
157
+ 'user-agent': userAgent,
158
+ },
159
+ body: new URLSearchParams({
160
+ client_id: clientId,
161
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
162
+ device_code,
163
+ }),
164
+ signal: AbortSignal.timeout(10 * 1000),
165
+ }),
166
+ ];
167
+ } catch (error) {
168
+ if (error instanceof Error) return [error];
169
+ return [
170
+ new Error('An unknown error occurred. See the logs for details.', {
171
+ cause: error,
172
+ }),
173
+ ];
174
+ }
175
+ },
176
+ /**
177
+ * Process the Token request Response
178
+ *
179
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
180
+ */
181
+ async processTokenResponse(
182
+ response: Response
183
+ ): Promise<[OAuthError] | [null, TokenSet]> {
184
+ const json = await response.json();
185
+ const processed = TokenSet.safeParse(json);
186
+
187
+ if (!processed.success) {
188
+ return [
189
+ new OAuthError(
190
+ `Failed to parse token response: ${processed.error.message}`,
191
+ json
192
+ ),
193
+ ];
194
+ }
195
+
196
+ return [null, processed.data];
197
+ },
198
+ /**
199
+ * Perform a Token Revocation Request.
200
+ *
201
+ * @see https://datatracker.ietf.org/doc/html/rfc7009#section-2.1
202
+ * @see https://datatracker.ietf.org/doc/html/rfc7009#section-2.2
203
+ */
204
+ async revokeToken(token: string): Promise<OAuthError | undefined> {
205
+ assertAs(as);
206
+ const response = await fetch(as.revocation_endpoint, {
207
+ method: 'POST',
208
+ headers: {
209
+ 'Content-Type': 'application/x-www-form-urlencoded',
210
+ 'user-agent': userAgent,
211
+ },
212
+ body: new URLSearchParams({ token, client_id: clientId }),
213
+ });
214
+
215
+ if (response.ok) return;
216
+ const json = await response.json();
217
+
218
+ return new OAuthError('Revocation request failed', json);
219
+ },
220
+ /**
221
+ * Perform Refresh Token Request.
222
+ *
223
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-6
224
+ */
225
+ async refreshToken(token: string): Promise<TokenSet> {
226
+ assertAs(as);
227
+ const response = await fetch(as.token_endpoint, {
228
+ method: 'POST',
229
+ headers: {
230
+ 'Content-Type': 'application/x-www-form-urlencoded',
231
+ 'user-agent': userAgent,
232
+ },
233
+ body: new URLSearchParams({
234
+ client_id: clientId,
235
+ grant_type: 'refresh_token',
236
+ refresh_token: token,
237
+ }),
238
+ });
239
+
240
+ const [tokensError, tokenSet] = await this.processTokenResponse(response);
241
+ if (tokensError) throw tokensError;
242
+ return tokenSet;
243
+ },
244
+ /**
245
+ * Perform Token Introspection Request.
246
+ *
247
+ * @see https://datatracker.ietf.org/doc/html/rfc7662#section-2.1
248
+ */
249
+ async introspectToken(
250
+ token: string
251
+ ): Promise<z.infer<typeof IntrospectionResponse>> {
252
+ assertAs(as);
253
+ const response = await fetch(as.introspection_endpoint, {
254
+ method: 'POST',
255
+ headers: {
256
+ 'Content-Type': 'application/x-www-form-urlencoded',
257
+ 'user-agent': userAgent,
258
+ },
259
+ body: new URLSearchParams({ token }),
260
+ });
261
+
262
+ const json = await response.json();
263
+ const processed = IntrospectionResponse.safeParse(json);
264
+ if (!processed.success) {
265
+ throw new OAuthError(
266
+ `Failed to parse introspection response: ${processed.error.message}`,
267
+ json
268
+ );
269
+ }
270
+
271
+ return processed.data;
272
+ },
273
+ };
274
+ }
275
+
276
+ export type OAuth = ReturnType<typeof OAuth>;
277
+
278
+ const TokenSet = z.object({
279
+ /** The access token issued by the authorization server. */
280
+ access_token: z.string(),
281
+ /** The type of the token issued */
282
+ token_type: z.literal('Bearer'),
283
+ /** The lifetime in seconds of the access token. */
284
+ expires_in: z.number(),
285
+ /** The refresh token, which can be used to obtain new access tokens. */
286
+ refresh_token: z.optional(z.string()),
287
+ /** The scope of the access token. */
288
+ scope: z.optional(z.string()),
289
+ });
290
+
291
+ type TokenSet = z.infer<typeof TokenSet>;
292
+
293
+ const OAuthErrorResponse = z.object({
294
+ error: z.enum([
295
+ 'invalid_request',
296
+ 'invalid_client',
297
+ 'invalid_grant',
298
+ 'unauthorized_client',
299
+ 'unsupported_grant_type',
300
+ 'invalid_scope',
301
+ 'server_error',
302
+ // Device Authorization Response Errors
303
+ 'authorization_pending',
304
+ 'slow_down',
305
+ 'access_denied',
306
+ 'expired_token',
307
+ // Revocation Response Errors
308
+ 'unsupported_token_type',
309
+ ]),
310
+ error_description: z.optional(z.string()),
311
+ error_uri: z.optional(z.string()),
312
+ });
313
+
314
+ type OAuthErrorResponse = z.infer<typeof OAuthErrorResponse>;
315
+
316
+ function processOAuthErrorResponse(
317
+ json: unknown
318
+ ): OAuthErrorResponse | TypeError {
319
+ try {
320
+ return OAuthErrorResponse.parse(json);
321
+ } catch (error) {
322
+ if (error instanceof z.core.$ZodError) {
323
+ return new TypeError(`Invalid OAuth error response: ${error.message}`);
324
+ }
325
+ return new TypeError('Failed to parse OAuth error response');
326
+ }
327
+ }
328
+
329
+ class OAuthError extends Error {
330
+ name = 'OAuthError';
331
+ code: OAuthErrorResponse['error'];
332
+ cause: Error;
333
+ constructor(message: string, response: unknown) {
334
+ super(message);
335
+ const error = processOAuthErrorResponse(response);
336
+ if (error instanceof TypeError) {
337
+ const message = `Unexpected server response: ${JSON.stringify(response)}`;
338
+ this.cause = new Error(message, { cause: error });
339
+ this.code = 'server_error';
340
+ return;
341
+ }
342
+ let cause = error.error;
343
+ if (error.error_description) cause += `: ${error.error_description}`;
344
+ if (error.error_uri) cause += ` (${error.error_uri})`;
345
+
346
+ this.cause = new Error(cause.toString());
347
+ this.code = error.error;
348
+ }
349
+ }
350
+
351
+ export function isOAuthError(error: unknown): error is OAuthError {
352
+ return error instanceof OAuthError;
353
+ }