@rudderjs/passport 1.1.1 → 1.1.3
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 +96 -15
- package/dist/grants/authorization-code.d.ts.map +1 -1
- package/dist/grants/authorization-code.js +4 -17
- package/dist/grants/authorization-code.js.map +1 -1
- package/dist/grants/client-credentials.d.ts.map +1 -1
- package/dist/grants/client-credentials.js +4 -17
- package/dist/grants/client-credentials.js.map +1 -1
- package/dist/grants/device-code.d.ts.map +1 -1
- package/dist/grants/device-code.js +2 -1
- package/dist/grants/device-code.js.map +1 -1
- package/dist/grants/parse-scopes.d.ts +15 -0
- package/dist/grants/parse-scopes.d.ts.map +1 -0
- package/dist/grants/parse-scopes.js +17 -0
- package/dist/grants/parse-scopes.js.map +1 -0
- package/dist/grants/refresh-token.d.ts.map +1 -1
- package/dist/grants/refresh-token.js +5 -18
- package/dist/grants/refresh-token.js.map +1 -1
- package/dist/grants/verify-client.d.ts +29 -0
- package/dist/grants/verify-client.d.ts.map +1 -0
- package/dist/grants/verify-client.js +43 -0
- package/dist/grants/verify-client.js.map +1 -0
- package/dist/middleware/bearer.d.ts.map +1 -1
- package/dist/middleware/bearer.js +98 -103
- package/dist/middleware/bearer.js.map +1 -1
- package/dist/models/AccessToken.d.ts +3 -3
- package/dist/models/AuthCode.d.ts +3 -3
- package/dist/models/DeviceCode.d.ts +3 -3
- package/dist/models/RefreshToken.d.ts +3 -3
- package/dist/models/helpers.d.ts +27 -9
- package/dist/models/helpers.d.ts.map +1 -1
- package/dist/models/helpers.js +12 -6
- package/dist/models/helpers.js.map +1 -1
- package/dist/personal-access-tokens.d.ts.map +1 -1
- package/dist/personal-access-tokens.js.map +1 -1
- package/dist/routes/authorize.d.ts +17 -0
- package/dist/routes/authorize.d.ts.map +1 -0
- package/dist/routes/authorize.js +107 -0
- package/dist/routes/authorize.js.map +1 -0
- package/dist/routes/device.d.ts +23 -0
- package/dist/routes/device.d.ts.map +1 -0
- package/dist/routes/device.js +69 -0
- package/dist/routes/device.js.map +1 -0
- package/dist/routes/helpers.d.ts +64 -0
- package/dist/routes/helpers.d.ts.map +1 -0
- package/dist/routes/helpers.js +154 -0
- package/dist/routes/helpers.js.map +1 -0
- package/dist/routes/revoke.d.ts +16 -0
- package/dist/routes/revoke.d.ts.map +1 -0
- package/dist/routes/revoke.js +33 -0
- package/dist/routes/revoke.js.map +1 -0
- package/dist/routes/scopes.d.ts +9 -0
- package/dist/routes/scopes.d.ts.map +1 -0
- package/dist/routes/scopes.js +13 -0
- package/dist/routes/scopes.js.map +1 -0
- package/dist/routes/token.d.ts +24 -0
- package/dist/routes/token.d.ts.map +1 -0
- package/dist/routes/token.js +121 -0
- package/dist/routes/token.js.map +1 -0
- package/dist/routes/types.d.ts +132 -0
- package/dist/routes/types.d.ts.map +1 -0
- package/dist/routes/types.js +2 -0
- package/dist/routes/types.js.map +1 -0
- package/dist/routes.d.ts +2 -120
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +16 -411
- package/dist/routes.js.map +1 -1
- package/package.json +8 -5
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from '@rudderjs/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal handler signature. Kept `any`-typed because passport routes are
|
|
4
|
+
* mounted on arbitrary Router implementations (Hono adapter, express-style,
|
|
5
|
+
* test fakes) — pinning a concrete request/response type here would force
|
|
6
|
+
* downstream casts everywhere a handler is implemented.
|
|
7
|
+
*/
|
|
8
|
+
export type RouteHandler = (req: any, res: any) => Promise<any> | any;
|
|
9
|
+
/**
|
|
10
|
+
* Minimal Router contract passport's `register*Routes()` functions accept.
|
|
11
|
+
* The `@rudderjs/router` instance satisfies this, as do simple test fakes —
|
|
12
|
+
* we don't import the concrete class here to keep this package usable from
|
|
13
|
+
* apps that bring their own router.
|
|
14
|
+
*/
|
|
15
|
+
export interface Router {
|
|
16
|
+
get(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
17
|
+
post(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
18
|
+
delete(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
19
|
+
}
|
|
20
|
+
/** Groups of routes that can be selectively excluded. */
|
|
21
|
+
export type PassportRouteGroup = 'authorize' | 'token' | 'revoke' | 'scopes' | 'device';
|
|
22
|
+
export interface PassportRouteOptions {
|
|
23
|
+
/** Base path for OAuth routes (default: '/oauth') */
|
|
24
|
+
prefix?: string;
|
|
25
|
+
/** Verification URI for device auth (default: '{origin}/oauth/device') */
|
|
26
|
+
verificationUri?: string;
|
|
27
|
+
/** Route groups to skip when registering. */
|
|
28
|
+
except?: PassportRouteGroup[];
|
|
29
|
+
/**
|
|
30
|
+
* Middleware applied to `POST /oauth/token`. The token endpoint is the
|
|
31
|
+
* canonical brute-force target for client_secret guessing — every
|
|
32
|
+
* production app SHOULD mount a per-route rate limiter here.
|
|
33
|
+
*
|
|
34
|
+
* Recommended setup:
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { RateLimit } from '@rudderjs/middleware'
|
|
38
|
+
* import { registerPassportRoutes } from '@rudderjs/passport'
|
|
39
|
+
*
|
|
40
|
+
* registerPassportRoutes(router, {
|
|
41
|
+
* tokenMiddleware: [
|
|
42
|
+
* RateLimit.perMinute(10).by((req) => `${req.ip}:${req.body?.client_id}`),
|
|
43
|
+
* ],
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* The composite key (`ip + client_id`) prevents one noisy client from
|
|
48
|
+
* exhausting the budget for legitimate co-tenants behind a shared NAT,
|
|
49
|
+
* and prevents a single IP from churning through every client_id in the
|
|
50
|
+
* registry. RateLimit also requires a cache provider to be registered —
|
|
51
|
+
* see `@rudderjs/cache`. Without one the middleware silently passes
|
|
52
|
+
* through.
|
|
53
|
+
*
|
|
54
|
+
* Accepts a single handler or an array. Empty / omitted means no
|
|
55
|
+
* additional middleware is applied (the same as before this option
|
|
56
|
+
* existed).
|
|
57
|
+
*/
|
|
58
|
+
tokenMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
59
|
+
/**
|
|
60
|
+
* Middleware applied to the consent endpoints — `GET/POST/DELETE
|
|
61
|
+
* /oauth/authorize` and `DELETE /oauth/tokens/:id`. POST /oauth/authorize
|
|
62
|
+
* is the canonical CSRF target (an attacker page that auto-submits a
|
|
63
|
+
* hidden form would mint authorization codes for the victim's logged-in
|
|
64
|
+
* session).
|
|
65
|
+
*
|
|
66
|
+
* Most apps should NOT use this option. The recommended pattern is to
|
|
67
|
+
* mount CSRF on the entire web group from `bootstrap/app.ts`:
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* .withMiddleware((m) => m.web(CsrfMiddleware()))
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* which automatically covers `/oauth/authorize` along with every other
|
|
74
|
+
* state-changing web route. `authorizeMiddleware` is the per-route
|
|
75
|
+
* fallback for apps that do NOT mount CSRF at the group level:
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { CsrfMiddleware } from '@rudderjs/middleware'
|
|
79
|
+
* import { registerPassportWebRoutes } from '@rudderjs/passport'
|
|
80
|
+
*
|
|
81
|
+
* registerPassportWebRoutes(router, {
|
|
82
|
+
* authorizeMiddleware: [CsrfMiddleware()],
|
|
83
|
+
* })
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* Don't do both — CsrfMiddleware running twice on the same request
|
|
87
|
+
* emits duplicate `Set-Cookie`s on GETs and runs validation twice on
|
|
88
|
+
* POSTs.
|
|
89
|
+
*
|
|
90
|
+
* Accepts a single handler or an array. Empty / omitted means no
|
|
91
|
+
* additional middleware is applied — the typical case for apps that
|
|
92
|
+
* already CSRF-guard at the group level.
|
|
93
|
+
*/
|
|
94
|
+
authorizeMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
95
|
+
/**
|
|
96
|
+
* Middleware applied to the device-flow endpoints — `POST /oauth/device/code`
|
|
97
|
+
* and `POST /oauth/device/approve`. RFC 8628 §5.2 calls for brute-force
|
|
98
|
+
* protection on the user_code surface (8-char alphabet → 32^8 ≈ 1.1×10^12
|
|
99
|
+
* keyspace; per-IP throttling makes exhaustion infeasible).
|
|
100
|
+
*
|
|
101
|
+
* Most apps should NOT need this option. The recommended pattern is to
|
|
102
|
+
* mount a rate limiter on the entire api group from `bootstrap/app.ts`
|
|
103
|
+
* (`withMiddleware((m) => m.api(RateLimit.perMinute(60)))`) — that single
|
|
104
|
+
* hook covers the device endpoints alongside every other api route, and
|
|
105
|
+
* 60/min per-IP is already enough that exhausting the user_code keyspace
|
|
106
|
+
* would take tens of thousands of years.
|
|
107
|
+
*
|
|
108
|
+
* `deviceMiddleware` is the per-route fallback for apps that want a
|
|
109
|
+
* tighter device-specific limit (e.g. `RateLimit.perMinute(5)`) on top of
|
|
110
|
+
* — or in place of — the group default:
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* import { RateLimit } from '@rudderjs/middleware'
|
|
114
|
+
* import { registerPassportApiRoutes } from '@rudderjs/passport'
|
|
115
|
+
*
|
|
116
|
+
* registerPassportApiRoutes(router, {
|
|
117
|
+
* deviceMiddleware: [RateLimit.perMinute(5).by((req) => req.ip)],
|
|
118
|
+
* })
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* Layered limits compose in sequence — group + per-route both run, with
|
|
122
|
+
* the tightest budget winning. Locking individual user_codes after N
|
|
123
|
+
* misses (the stateful half of the original RFC 8628 §5.2 guidance)
|
|
124
|
+
* isn't covered by RateLimit; if you need it, wrap your own middleware.
|
|
125
|
+
*
|
|
126
|
+
* Accepts a single handler or an array. Empty / omitted means no
|
|
127
|
+
* additional middleware is applied — the typical case for apps that
|
|
128
|
+
* already throttle the api group.
|
|
129
|
+
*/
|
|
130
|
+
deviceMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/routes/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;AAErE;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IACpE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IACrE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;CACxE;AAED,yDAAyD;AACzD,MAAM,MAAM,kBAAkB,GAC1B,WAAW,GACX,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,QAAQ,CAAA;AAEZ,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,eAAe,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,CAAA;IACzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,mBAAmB,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,CAAA;IAC7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,CAAA;CAC3D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/routes/types.ts"],"names":[],"mappings":""}
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,122 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
type
|
|
3
|
-
interface Router {
|
|
4
|
-
get(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
5
|
-
post(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
6
|
-
delete(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
7
|
-
}
|
|
8
|
-
/** Groups of routes that can be selectively excluded. */
|
|
9
|
-
export type PassportRouteGroup = 'authorize' | 'token' | 'revoke' | 'scopes' | 'device';
|
|
10
|
-
export interface PassportRouteOptions {
|
|
11
|
-
/** Base path for OAuth routes (default: '/oauth') */
|
|
12
|
-
prefix?: string;
|
|
13
|
-
/** Verification URI for device auth (default: '{origin}/oauth/device') */
|
|
14
|
-
verificationUri?: string;
|
|
15
|
-
/** Route groups to skip when registering. */
|
|
16
|
-
except?: PassportRouteGroup[];
|
|
17
|
-
/**
|
|
18
|
-
* Middleware applied to `POST /oauth/token`. The token endpoint is the
|
|
19
|
-
* canonical brute-force target for client_secret guessing — every
|
|
20
|
-
* production app SHOULD mount a per-route rate limiter here.
|
|
21
|
-
*
|
|
22
|
-
* Recommended setup:
|
|
23
|
-
*
|
|
24
|
-
* ```ts
|
|
25
|
-
* import { RateLimit } from '@rudderjs/middleware'
|
|
26
|
-
* import { registerPassportRoutes } from '@rudderjs/passport'
|
|
27
|
-
*
|
|
28
|
-
* registerPassportRoutes(router, {
|
|
29
|
-
* tokenMiddleware: [
|
|
30
|
-
* RateLimit.perMinute(10).by((req) => `${req.ip}:${req.body?.client_id}`),
|
|
31
|
-
* ],
|
|
32
|
-
* })
|
|
33
|
-
* ```
|
|
34
|
-
*
|
|
35
|
-
* The composite key (`ip + client_id`) prevents one noisy client from
|
|
36
|
-
* exhausting the budget for legitimate co-tenants behind a shared NAT,
|
|
37
|
-
* and prevents a single IP from churning through every client_id in the
|
|
38
|
-
* registry. RateLimit also requires a cache provider to be registered —
|
|
39
|
-
* see `@rudderjs/cache`. Without one the middleware silently passes
|
|
40
|
-
* through.
|
|
41
|
-
*
|
|
42
|
-
* Accepts a single handler or an array. Empty / omitted means no
|
|
43
|
-
* additional middleware is applied (the same as before this option
|
|
44
|
-
* existed).
|
|
45
|
-
*/
|
|
46
|
-
tokenMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
47
|
-
/**
|
|
48
|
-
* Middleware applied to the consent endpoints — `GET/POST/DELETE
|
|
49
|
-
* /oauth/authorize` and `DELETE /oauth/tokens/:id`. POST /oauth/authorize
|
|
50
|
-
* is the canonical CSRF target (an attacker page that auto-submits a
|
|
51
|
-
* hidden form would mint authorization codes for the victim's logged-in
|
|
52
|
-
* session).
|
|
53
|
-
*
|
|
54
|
-
* Most apps should NOT use this option. The recommended pattern is to
|
|
55
|
-
* mount CSRF on the entire web group from `bootstrap/app.ts`:
|
|
56
|
-
*
|
|
57
|
-
* ```ts
|
|
58
|
-
* .withMiddleware((m) => m.web(CsrfMiddleware()))
|
|
59
|
-
* ```
|
|
60
|
-
*
|
|
61
|
-
* which automatically covers `/oauth/authorize` along with every other
|
|
62
|
-
* state-changing web route. `authorizeMiddleware` is the per-route
|
|
63
|
-
* fallback for apps that do NOT mount CSRF at the group level:
|
|
64
|
-
*
|
|
65
|
-
* ```ts
|
|
66
|
-
* import { CsrfMiddleware } from '@rudderjs/middleware'
|
|
67
|
-
* import { registerPassportWebRoutes } from '@rudderjs/passport'
|
|
68
|
-
*
|
|
69
|
-
* registerPassportWebRoutes(router, {
|
|
70
|
-
* authorizeMiddleware: [CsrfMiddleware()],
|
|
71
|
-
* })
|
|
72
|
-
* ```
|
|
73
|
-
*
|
|
74
|
-
* Don't do both — CsrfMiddleware running twice on the same request
|
|
75
|
-
* emits duplicate `Set-Cookie`s on GETs and runs validation twice on
|
|
76
|
-
* POSTs.
|
|
77
|
-
*
|
|
78
|
-
* Accepts a single handler or an array. Empty / omitted means no
|
|
79
|
-
* additional middleware is applied — the typical case for apps that
|
|
80
|
-
* already CSRF-guard at the group level.
|
|
81
|
-
*/
|
|
82
|
-
authorizeMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
83
|
-
/**
|
|
84
|
-
* Middleware applied to the device-flow endpoints — `POST /oauth/device/code`
|
|
85
|
-
* and `POST /oauth/device/approve`. RFC 8628 §5.2 calls for brute-force
|
|
86
|
-
* protection on the user_code surface (8-char alphabet → 32^8 ≈ 1.1×10^12
|
|
87
|
-
* keyspace; per-IP throttling makes exhaustion infeasible).
|
|
88
|
-
*
|
|
89
|
-
* Most apps should NOT need this option. The recommended pattern is to
|
|
90
|
-
* mount a rate limiter on the entire api group from `bootstrap/app.ts`
|
|
91
|
-
* (`withMiddleware((m) => m.api(RateLimit.perMinute(60)))`) — that single
|
|
92
|
-
* hook covers the device endpoints alongside every other api route, and
|
|
93
|
-
* 60/min per-IP is already enough that exhausting the user_code keyspace
|
|
94
|
-
* would take tens of thousands of years.
|
|
95
|
-
*
|
|
96
|
-
* `deviceMiddleware` is the per-route fallback for apps that want a
|
|
97
|
-
* tighter device-specific limit (e.g. `RateLimit.perMinute(5)`) on top of
|
|
98
|
-
* — or in place of — the group default:
|
|
99
|
-
*
|
|
100
|
-
* ```ts
|
|
101
|
-
* import { RateLimit } from '@rudderjs/middleware'
|
|
102
|
-
* import { registerPassportApiRoutes } from '@rudderjs/passport'
|
|
103
|
-
*
|
|
104
|
-
* registerPassportApiRoutes(router, {
|
|
105
|
-
* deviceMiddleware: [RateLimit.perMinute(5).by((req) => req.ip)],
|
|
106
|
-
* })
|
|
107
|
-
* ```
|
|
108
|
-
*
|
|
109
|
-
* Layered limits compose in sequence — group + per-route both run, with
|
|
110
|
-
* the tightest budget winning. Locking individual user_codes after N
|
|
111
|
-
* misses (the stateful half of the original RFC 8628 §5.2 guidance)
|
|
112
|
-
* isn't covered by RateLimit; if you need it, wrap your own middleware.
|
|
113
|
-
*
|
|
114
|
-
* Accepts a single handler or an array. Empty / omitted means no
|
|
115
|
-
* additional middleware is applied — the typical case for apps that
|
|
116
|
-
* already throttle the api group.
|
|
117
|
-
*/
|
|
118
|
-
deviceMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
119
|
-
}
|
|
1
|
+
import type { PassportRouteOptions, Router } from './routes/types.js';
|
|
2
|
+
export type { PassportRouteGroup, PassportRouteOptions } from './routes/types.js';
|
|
120
3
|
/**
|
|
121
4
|
* Register all Passport OAuth routes on the given router.
|
|
122
5
|
*
|
|
@@ -178,5 +61,4 @@ export declare function registerPassportWebRoutes(router: Router, opts?: Passpor
|
|
|
178
61
|
* stateful half on the web group.
|
|
179
62
|
*/
|
|
180
63
|
export declare function registerPassportApiRoutes(router: Router, opts?: PassportRouteOptions): void;
|
|
181
|
-
export {};
|
|
182
64
|
//# sourceMappingURL=routes.d.ts.map
|
package/dist/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAsB,oBAAoB,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAQzF,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAEjF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,oBAAyB,GAAG,IAAI,CAc5F;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,oBAAyB,GAAG,IAAI,CAG/F;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,oBAAyB,GAAG,IAAI,CAG/F"}
|