@nya-account/node-sdk 2.0.0 → 2.0.2
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/README.md +187 -172
- package/dist/{express-yO7hxKKd.d.ts → express-Bn8IUnft.d.ts} +10 -4
- package/dist/express-Bn8IUnft.d.ts.map +1 -0
- package/dist/express.d.ts +1 -1
- package/dist/index.d.ts +60 -45
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +103 -153
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/express-yO7hxKKd.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
import { createRemoteJWKSet, errors, jwtVerify } from "jose";
|
|
6
6
|
|
|
7
7
|
//#region src/core/schemas.ts
|
|
8
|
+
const TokenTypeHintSchema = z.enum(["access_token", "refresh_token"]);
|
|
8
9
|
const TokenResponseSchema = z.object({
|
|
9
10
|
access_token: z.string(),
|
|
10
11
|
token_type: z.string(),
|
|
@@ -28,12 +29,14 @@ const IntrospectionResponseSchema = z.object({
|
|
|
28
29
|
sub: z.string().optional(),
|
|
29
30
|
aud: z.string().optional(),
|
|
30
31
|
iss: z.string().optional(),
|
|
31
|
-
jti: z.string().optional()
|
|
32
|
+
jti: z.string().optional(),
|
|
33
|
+
sid: z.string().optional(),
|
|
34
|
+
sv: z.number().optional()
|
|
32
35
|
});
|
|
33
36
|
const UserInfoSchema = z.object({
|
|
34
37
|
sub: z.string(),
|
|
35
38
|
name: z.string().optional(),
|
|
36
|
-
|
|
39
|
+
picture: z.string().optional(),
|
|
37
40
|
email: z.string().optional(),
|
|
38
41
|
email_verified: z.boolean().optional(),
|
|
39
42
|
updated_at: z.number().optional()
|
|
@@ -60,12 +63,18 @@ const DiscoveryDocumentSchema = z.object({
|
|
|
60
63
|
request_parameter_supported: z.boolean().optional(),
|
|
61
64
|
request_uri_parameter_supported: z.boolean().optional()
|
|
62
65
|
});
|
|
66
|
+
const PushedAuthorizationResponseSchema = z.object({
|
|
67
|
+
request_uri: z.string().min(1),
|
|
68
|
+
expires_in: z.number().int().positive()
|
|
69
|
+
});
|
|
63
70
|
const AccessTokenPayloadSchema = z.object({
|
|
64
71
|
iss: z.string(),
|
|
65
72
|
sub: z.string(),
|
|
66
73
|
aud: z.string(),
|
|
67
74
|
scope: z.string(),
|
|
68
75
|
ver: z.string(),
|
|
76
|
+
sid: z.string(),
|
|
77
|
+
sv: z.number().int().nonnegative(),
|
|
69
78
|
iat: z.number(),
|
|
70
79
|
exp: z.number(),
|
|
71
80
|
jti: z.string(),
|
|
@@ -75,11 +84,11 @@ const IdTokenPayloadSchema = z.object({
|
|
|
75
84
|
iss: z.string(),
|
|
76
85
|
sub: z.string(),
|
|
77
86
|
aud: z.string(),
|
|
87
|
+
sid: z.string().optional(),
|
|
78
88
|
iat: z.number(),
|
|
79
89
|
exp: z.number(),
|
|
80
90
|
nonce: z.string().optional(),
|
|
81
91
|
name: z.string().optional(),
|
|
82
|
-
preferred_username: z.string().optional(),
|
|
83
92
|
email: z.string().optional(),
|
|
84
93
|
email_verified: z.boolean().optional(),
|
|
85
94
|
updated_at: z.number().optional()
|
|
@@ -235,6 +244,7 @@ var JwtVerifier = class {
|
|
|
235
244
|
//#region src/client.ts
|
|
236
245
|
const DISCOVERY_ENDPOINT_MAP = {
|
|
237
246
|
authorization: "authorizationEndpoint",
|
|
247
|
+
pushedAuthorizationRequest: "pushedAuthorizationRequestEndpoint",
|
|
238
248
|
token: "tokenEndpoint",
|
|
239
249
|
userinfo: "userinfoEndpoint",
|
|
240
250
|
revocation: "revocationEndpoint",
|
|
@@ -242,79 +252,10 @@ const DISCOVERY_ENDPOINT_MAP = {
|
|
|
242
252
|
jwks: "jwksUri",
|
|
243
253
|
endSession: "endSessionEndpoint"
|
|
244
254
|
};
|
|
255
|
+
/** Default issuer URL */
|
|
256
|
+
const DEFAULT_ISSUER = "https://account-api.edge.lolinya.net";
|
|
245
257
|
/** Default discovery cache TTL: 1 hour */
|
|
246
258
|
const DEFAULT_DISCOVERY_CACHE_TTL = 36e5;
|
|
247
|
-
/**
|
|
248
|
-
|
|
249
|
-
* Nya Account Node.js SDK client.
|
|
250
|
-
|
|
251
|
-
*
|
|
252
|
-
|
|
253
|
-
* Provides full OAuth 2.1 / OIDC flow support:
|
|
254
|
-
|
|
255
|
-
* - Authorization Code + PKCE
|
|
256
|
-
|
|
257
|
-
* - Token exchange / refresh / revocation / introspection
|
|
258
|
-
|
|
259
|
-
* - Local JWT verification (via JWKS)
|
|
260
|
-
|
|
261
|
-
* - OIDC UserInfo
|
|
262
|
-
|
|
263
|
-
* - OIDC Discovery auto-discovery
|
|
264
|
-
|
|
265
|
-
* - Express middleware (Bearer Token auth + scope validation)
|
|
266
|
-
|
|
267
|
-
*
|
|
268
|
-
|
|
269
|
-
* @example
|
|
270
|
-
|
|
271
|
-
* ```typescript
|
|
272
|
-
|
|
273
|
-
* const client = new NyaAccountClient({
|
|
274
|
-
|
|
275
|
-
* issuer: 'https://account.example.com',
|
|
276
|
-
|
|
277
|
-
* clientId: 'my-app',
|
|
278
|
-
|
|
279
|
-
* clientSecret: 'my-secret',
|
|
280
|
-
|
|
281
|
-
* })
|
|
282
|
-
|
|
283
|
-
*
|
|
284
|
-
|
|
285
|
-
* // Create authorization URL (with PKCE)
|
|
286
|
-
|
|
287
|
-
* const { url, codeVerifier, state } = await client.createAuthorizationUrl({
|
|
288
|
-
|
|
289
|
-
* redirectUri: 'https://myapp.com/callback',
|
|
290
|
-
|
|
291
|
-
* scope: 'openid profile email',
|
|
292
|
-
|
|
293
|
-
* })
|
|
294
|
-
|
|
295
|
-
*
|
|
296
|
-
|
|
297
|
-
* // Exchange code for tokens
|
|
298
|
-
|
|
299
|
-
* const tokens = await client.exchangeCode({
|
|
300
|
-
|
|
301
|
-
* code: callbackCode,
|
|
302
|
-
|
|
303
|
-
* redirectUri: 'https://myapp.com/callback',
|
|
304
|
-
|
|
305
|
-
* codeVerifier,
|
|
306
|
-
|
|
307
|
-
* })
|
|
308
|
-
|
|
309
|
-
*
|
|
310
|
-
|
|
311
|
-
* // Get user info
|
|
312
|
-
|
|
313
|
-
* const userInfo = await client.getUserInfo(tokens.accessToken)
|
|
314
|
-
|
|
315
|
-
* ```
|
|
316
|
-
|
|
317
|
-
*/
|
|
318
259
|
var NyaAccountClient = class {
|
|
319
260
|
constructor(config) {
|
|
320
261
|
this.httpClient = void 0;
|
|
@@ -323,14 +264,15 @@ var NyaAccountClient = class {
|
|
|
323
264
|
this.discoveryCacheTimestamp = 0;
|
|
324
265
|
this.discoveryCacheTtl = void 0;
|
|
325
266
|
this.jwtVerifier = null;
|
|
326
|
-
this.config =
|
|
267
|
+
this.config = {
|
|
268
|
+
...config,
|
|
269
|
+
issuer: config.issuer ?? DEFAULT_ISSUER
|
|
270
|
+
};
|
|
327
271
|
this.discoveryCacheTtl = config.discoveryCacheTtl ?? DEFAULT_DISCOVERY_CACHE_TTL;
|
|
328
272
|
this.httpClient = axios.create({ timeout: config.timeout ?? 1e4 });
|
|
329
273
|
}
|
|
330
274
|
/**
|
|
331
|
-
|
|
332
275
|
* Fetch the OIDC Discovery document. Results are cached with a configurable TTL.
|
|
333
|
-
|
|
334
276
|
*/
|
|
335
277
|
async discover() {
|
|
336
278
|
if (this.discoveryCache && Date.now() - this.discoveryCacheTimestamp < this.discoveryCacheTtl) return this.discoveryCache;
|
|
@@ -366,9 +308,7 @@ var NyaAccountClient = class {
|
|
|
366
308
|
}
|
|
367
309
|
}
|
|
368
310
|
/**
|
|
369
|
-
|
|
370
311
|
* Clear the cached Discovery document and JWT verifier, forcing a re-fetch on next use.
|
|
371
|
-
|
|
372
312
|
*/
|
|
373
313
|
clearCache() {
|
|
374
314
|
this.discoveryCache = null;
|
|
@@ -376,15 +316,10 @@ var NyaAccountClient = class {
|
|
|
376
316
|
this.jwtVerifier = null;
|
|
377
317
|
}
|
|
378
318
|
/**
|
|
379
|
-
|
|
380
319
|
* Create an OAuth authorization URL (automatically includes PKCE).
|
|
381
|
-
|
|
382
320
|
*
|
|
383
|
-
|
|
384
321
|
* The returned `codeVerifier` and `state` must be saved to the session
|
|
385
|
-
|
|
386
322
|
* for later use in token exchange and CSRF validation.
|
|
387
|
-
|
|
388
323
|
*/
|
|
389
324
|
async createAuthorizationUrl(options) {
|
|
390
325
|
const authorizationEndpoint = await this.resolveEndpoint("authorization");
|
|
@@ -408,9 +343,72 @@ var NyaAccountClient = class {
|
|
|
408
343
|
};
|
|
409
344
|
}
|
|
410
345
|
/**
|
|
411
|
-
|
|
346
|
+
* Push authorization parameters to PAR endpoint (RFC 9126).
|
|
347
|
+
*
|
|
348
|
+
* Returns a `request_uri` that can be used in the authorization endpoint.
|
|
349
|
+
*/
|
|
350
|
+
async pushAuthorizationRequest(options) {
|
|
351
|
+
const parEndpoint = await this.resolveEndpoint("pushedAuthorizationRequest");
|
|
352
|
+
const codeVerifier = generateCodeVerifier();
|
|
353
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
354
|
+
const state = options.state ?? randomBytes(16).toString("base64url");
|
|
355
|
+
const payload = {
|
|
356
|
+
client_id: this.config.clientId,
|
|
357
|
+
client_secret: this.config.clientSecret,
|
|
358
|
+
redirect_uri: options.redirectUri,
|
|
359
|
+
response_type: "code",
|
|
360
|
+
scope: options.scope ?? "openid",
|
|
361
|
+
state,
|
|
362
|
+
code_challenge: codeChallenge,
|
|
363
|
+
code_challenge_method: "S256"
|
|
364
|
+
};
|
|
365
|
+
if (options.nonce) payload.nonce = options.nonce;
|
|
366
|
+
if (options.request) payload.request = options.request;
|
|
367
|
+
try {
|
|
368
|
+
const response = await this.httpClient.post(parEndpoint, payload);
|
|
369
|
+
const raw = PushedAuthorizationResponseSchema.parse(response.data);
|
|
370
|
+
return {
|
|
371
|
+
requestUri: raw.request_uri,
|
|
372
|
+
expiresIn: raw.expires_in,
|
|
373
|
+
codeVerifier,
|
|
374
|
+
state
|
|
375
|
+
};
|
|
376
|
+
} catch (error) {
|
|
377
|
+
throw this.handleTokenError(error);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Create an authorization URL using PAR `request_uri`.
|
|
382
|
+
*/
|
|
383
|
+
async createAuthorizationUrlWithPar(options) {
|
|
384
|
+
const authorizationEndpoint = await this.resolveEndpoint("authorization");
|
|
385
|
+
const pushed = await this.pushAuthorizationRequest(options);
|
|
386
|
+
const params = new URLSearchParams({
|
|
387
|
+
client_id: this.config.clientId,
|
|
388
|
+
request_uri: pushed.requestUri
|
|
389
|
+
});
|
|
390
|
+
return {
|
|
391
|
+
url: `${authorizationEndpoint}?${params.toString()}`,
|
|
392
|
+
codeVerifier: pushed.codeVerifier,
|
|
393
|
+
state: pushed.state,
|
|
394
|
+
requestUri: pushed.requestUri,
|
|
395
|
+
expiresIn: pushed.expiresIn
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Create OIDC RP-Initiated Logout URL (`end_session_endpoint`).
|
|
400
|
+
*/
|
|
401
|
+
async createEndSessionUrl(options) {
|
|
402
|
+
const endSessionEndpoint = await this.resolveEndpoint("endSession");
|
|
403
|
+
const params = new URLSearchParams();
|
|
404
|
+
if (options?.idTokenHint) params.set("id_token_hint", options.idTokenHint);
|
|
405
|
+
if (options?.postLogoutRedirectUri) params.set("post_logout_redirect_uri", options.postLogoutRedirectUri);
|
|
406
|
+
if (options?.state) params.set("state", options.state);
|
|
407
|
+
params.set("client_id", options?.clientId ?? this.config.clientId);
|
|
408
|
+
return `${endSessionEndpoint}?${params.toString()}`;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
412
411
|
* Exchange an authorization code for tokens (Authorization Code Grant).
|
|
413
|
-
|
|
414
412
|
*/
|
|
415
413
|
async exchangeCode(options) {
|
|
416
414
|
const tokenEndpoint = await this.resolveEndpoint("token");
|
|
@@ -429,9 +427,7 @@ var NyaAccountClient = class {
|
|
|
429
427
|
}
|
|
430
428
|
}
|
|
431
429
|
/**
|
|
432
|
-
|
|
433
430
|
* Refresh an Access Token using a Refresh Token.
|
|
434
|
-
|
|
435
431
|
*/
|
|
436
432
|
async refreshToken(refreshToken) {
|
|
437
433
|
const tokenEndpoint = await this.resolveEndpoint("token");
|
|
@@ -448,40 +444,35 @@ var NyaAccountClient = class {
|
|
|
448
444
|
}
|
|
449
445
|
}
|
|
450
446
|
/**
|
|
451
|
-
|
|
452
447
|
* Revoke a token (RFC 7009).
|
|
453
|
-
|
|
454
448
|
*
|
|
455
|
-
|
|
456
449
|
* Supports revoking Access Tokens or Refresh Tokens.
|
|
457
|
-
|
|
458
450
|
* Revoking a Refresh Token also revokes its entire token family.
|
|
459
|
-
|
|
460
451
|
*/
|
|
461
|
-
async revokeToken(token) {
|
|
452
|
+
async revokeToken(token, options) {
|
|
462
453
|
const revocationEndpoint = await this.resolveEndpoint("revocation");
|
|
463
454
|
try {
|
|
455
|
+
const tokenTypeHint = options?.tokenTypeHint ? TokenTypeHintSchema.parse(options.tokenTypeHint) : void 0;
|
|
464
456
|
await this.httpClient.post(revocationEndpoint, {
|
|
465
457
|
token,
|
|
458
|
+
token_type_hint: tokenTypeHint,
|
|
466
459
|
client_id: this.config.clientId,
|
|
467
460
|
client_secret: this.config.clientSecret
|
|
468
461
|
});
|
|
469
462
|
} catch {}
|
|
470
463
|
}
|
|
471
464
|
/**
|
|
472
|
-
|
|
473
465
|
* Token introspection (RFC 7662).
|
|
474
|
-
|
|
475
466
|
*
|
|
476
|
-
|
|
477
467
|
* Query the server for the current state of a token (active status, associated user info, etc.).
|
|
478
|
-
|
|
479
468
|
*/
|
|
480
|
-
async introspectToken(token) {
|
|
469
|
+
async introspectToken(token, options) {
|
|
481
470
|
const introspectionEndpoint = await this.resolveEndpoint("introspection");
|
|
482
471
|
try {
|
|
472
|
+
const tokenTypeHint = options?.tokenTypeHint ? TokenTypeHintSchema.parse(options.tokenTypeHint) : void 0;
|
|
483
473
|
const response = await this.httpClient.post(introspectionEndpoint, {
|
|
484
474
|
token,
|
|
475
|
+
token_type_hint: tokenTypeHint,
|
|
485
476
|
client_id: this.config.clientId,
|
|
486
477
|
client_secret: this.config.clientSecret
|
|
487
478
|
});
|
|
@@ -497,24 +488,20 @@ var NyaAccountClient = class {
|
|
|
497
488
|
sub: raw.sub,
|
|
498
489
|
aud: raw.aud,
|
|
499
490
|
iss: raw.iss,
|
|
500
|
-
jti: raw.jti
|
|
491
|
+
jti: raw.jti,
|
|
492
|
+
sid: raw.sid,
|
|
493
|
+
sv: raw.sv
|
|
501
494
|
};
|
|
502
495
|
} catch (error) {
|
|
503
496
|
throw this.handleTokenError(error);
|
|
504
497
|
}
|
|
505
498
|
}
|
|
506
499
|
/**
|
|
507
|
-
|
|
508
500
|
* Get user info using an Access Token (OIDC UserInfo Endpoint).
|
|
509
|
-
|
|
510
501
|
*
|
|
511
|
-
|
|
512
502
|
* The returned fields depend on the scopes included in the token:
|
|
513
|
-
|
|
514
|
-
* - `profile`: name, preferredUsername, updatedAt
|
|
515
|
-
|
|
503
|
+
* - `profile`: name, picture, updatedAt
|
|
516
504
|
* - `email`: email, emailVerified
|
|
517
|
-
|
|
518
505
|
*/
|
|
519
506
|
async getUserInfo(accessToken) {
|
|
520
507
|
const userinfoEndpoint = await this.resolveEndpoint("userinfo");
|
|
@@ -524,7 +511,7 @@ var NyaAccountClient = class {
|
|
|
524
511
|
return {
|
|
525
512
|
sub: raw.sub,
|
|
526
513
|
name: raw.name,
|
|
527
|
-
|
|
514
|
+
picture: raw.picture,
|
|
528
515
|
email: raw.email,
|
|
529
516
|
emailVerified: raw.email_verified,
|
|
530
517
|
updatedAt: raw.updated_at
|
|
@@ -534,77 +521,46 @@ var NyaAccountClient = class {
|
|
|
534
521
|
}
|
|
535
522
|
}
|
|
536
523
|
/**
|
|
537
|
-
|
|
538
524
|
* Locally verify a JWT Access Token (RFC 9068).
|
|
539
|
-
|
|
540
525
|
*
|
|
541
|
-
|
|
542
526
|
* Uses remote JWKS for signature verification, and validates issuer, audience, expiry, etc.
|
|
543
|
-
|
|
544
527
|
*
|
|
545
|
-
|
|
546
528
|
* @param token JWT Access Token string
|
|
547
|
-
|
|
548
529
|
* @param options.audience Custom audience validation value (defaults to clientId)
|
|
549
|
-
|
|
550
530
|
*/
|
|
551
531
|
async verifyAccessToken(token, options) {
|
|
552
532
|
const verifier = await this.getJwtVerifier();
|
|
553
533
|
return verifier.verifyAccessToken(token, options?.audience);
|
|
554
534
|
}
|
|
555
535
|
/**
|
|
556
|
-
|
|
557
536
|
* Locally verify an OIDC ID Token.
|
|
558
|
-
|
|
559
537
|
*
|
|
560
|
-
|
|
561
538
|
* @param token JWT ID Token string
|
|
562
|
-
|
|
563
539
|
* @param options.audience Custom audience validation value (defaults to clientId)
|
|
564
|
-
|
|
565
540
|
* @param options.nonce Validate the nonce claim (required if nonce was sent during authorization)
|
|
566
|
-
|
|
567
541
|
*/
|
|
568
542
|
async verifyIdToken(token, options) {
|
|
569
543
|
const verifier = await this.getJwtVerifier();
|
|
570
544
|
return verifier.verifyIdToken(token, options);
|
|
571
545
|
}
|
|
572
546
|
/**
|
|
573
|
-
|
|
574
547
|
* Express middleware: verify the Bearer Token in the request.
|
|
575
|
-
|
|
576
548
|
*
|
|
577
|
-
|
|
578
549
|
* After successful verification, use `getAuth(req)` to retrieve the token payload.
|
|
579
|
-
|
|
580
550
|
*
|
|
581
|
-
|
|
582
551
|
* @param options.strategy Verification strategy: 'local' (default, JWT local verification) or 'introspection' (remote introspection)
|
|
583
|
-
|
|
584
552
|
*
|
|
585
|
-
|
|
586
553
|
* @example
|
|
587
|
-
|
|
588
554
|
* ```typescript
|
|
589
|
-
|
|
590
555
|
* import { getAuth } from '@nya-account/node-sdk/express'
|
|
591
|
-
|
|
592
556
|
*
|
|
593
|
-
|
|
594
557
|
* app.use('/api', client.authenticate())
|
|
595
|
-
|
|
596
558
|
*
|
|
597
|
-
|
|
598
559
|
* app.get('/api/me', (req, res) => {
|
|
599
|
-
|
|
600
560
|
* const auth = getAuth(req)
|
|
601
|
-
|
|
602
561
|
* res.json({ userId: auth?.sub })
|
|
603
|
-
|
|
604
562
|
* })
|
|
605
|
-
|
|
606
563
|
* ```
|
|
607
|
-
|
|
608
564
|
*/
|
|
609
565
|
authenticate(options) {
|
|
610
566
|
const strategy = options?.strategy ?? "local";
|
|
@@ -622,6 +578,11 @@ var NyaAccountClient = class {
|
|
|
622
578
|
sendOAuthError(res, 401, "invalid_token", "Token is not active");
|
|
623
579
|
return;
|
|
624
580
|
}
|
|
581
|
+
const tokenType = introspection.tokenType?.toLowerCase();
|
|
582
|
+
if (tokenType && tokenType !== "bearer" && tokenType !== "access_token") {
|
|
583
|
+
sendOAuthError(res, 401, "invalid_token", "Token is not an access token");
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
625
586
|
payload = {
|
|
626
587
|
iss: introspection.iss ?? "",
|
|
627
588
|
sub: introspection.sub ?? "",
|
|
@@ -630,7 +591,9 @@ var NyaAccountClient = class {
|
|
|
630
591
|
ver: "1",
|
|
631
592
|
iat: introspection.iat ?? 0,
|
|
632
593
|
exp: introspection.exp ?? 0,
|
|
633
|
-
jti: introspection.jti ?? ""
|
|
594
|
+
jti: introspection.jti ?? "",
|
|
595
|
+
sid: introspection.sid ?? "",
|
|
596
|
+
sv: introspection.sv ?? 0
|
|
634
597
|
};
|
|
635
598
|
} else payload = await this.verifyAccessToken(token);
|
|
636
599
|
setAuth(req, payload);
|
|
@@ -642,31 +605,18 @@ var NyaAccountClient = class {
|
|
|
642
605
|
};
|
|
643
606
|
}
|
|
644
607
|
/**
|
|
645
|
-
|
|
646
608
|
* Express middleware: validate that the token in the request contains the specified scopes.
|
|
647
|
-
|
|
648
609
|
*
|
|
649
|
-
|
|
650
610
|
* Must be used after the `authenticate()` middleware.
|
|
651
|
-
|
|
652
611
|
*
|
|
653
|
-
|
|
654
612
|
* @example
|
|
655
|
-
|
|
656
613
|
* ```typescript
|
|
657
|
-
|
|
658
614
|
* app.get('/api/profile',
|
|
659
|
-
|
|
660
615
|
* client.authenticate(),
|
|
661
|
-
|
|
662
616
|
* client.requireScopes('profile'),
|
|
663
|
-
|
|
664
617
|
* (req, res) => { ... }
|
|
665
|
-
|
|
666
618
|
* )
|
|
667
|
-
|
|
668
619
|
* ```
|
|
669
|
-
|
|
670
620
|
*/
|
|
671
621
|
requireScopes(...scopes) {
|
|
672
622
|
return (req, res, next) => {
|