@shware/security 7.1.1 → 8.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.
Files changed (132) hide show
  1. package/README.md +113 -32
  2. package/dist/auth/attributes.cjs +4 -3
  3. package/dist/auth/attributes.cjs.map +1 -1
  4. package/dist/auth/attributes.d.cts +3 -3
  5. package/dist/auth/attributes.d.cts.map +1 -1
  6. package/dist/auth/attributes.d.mts +3 -3
  7. package/dist/auth/attributes.d.mts.map +1 -1
  8. package/dist/auth/attributes.mjs +4 -3
  9. package/dist/auth/attributes.mjs.map +1 -1
  10. package/dist/auth/desktop-codes.cjs +57 -0
  11. package/dist/auth/desktop-codes.cjs.map +1 -0
  12. package/dist/auth/desktop-codes.d.cts +24 -0
  13. package/dist/auth/desktop-codes.d.cts.map +1 -0
  14. package/dist/auth/desktop-codes.d.mts +24 -0
  15. package/dist/auth/desktop-codes.d.mts.map +1 -0
  16. package/dist/auth/desktop-codes.mjs +55 -0
  17. package/dist/auth/desktop-codes.mjs.map +1 -0
  18. package/dist/auth/email-codes.cjs +77 -0
  19. package/dist/auth/email-codes.cjs.map +1 -0
  20. package/dist/auth/email-codes.d.cts +39 -0
  21. package/dist/auth/email-codes.d.cts.map +1 -0
  22. package/dist/auth/email-codes.d.mts +39 -0
  23. package/dist/auth/email-codes.d.mts.map +1 -0
  24. package/dist/auth/email-codes.mjs +75 -0
  25. package/dist/auth/email-codes.mjs.map +1 -0
  26. package/dist/auth/error.cjs +47 -0
  27. package/dist/auth/error.cjs.map +1 -0
  28. package/dist/auth/error.d.cts +88 -0
  29. package/dist/auth/error.d.cts.map +1 -0
  30. package/dist/auth/error.d.mts +88 -0
  31. package/dist/auth/error.d.mts.map +1 -0
  32. package/dist/auth/error.mjs +45 -0
  33. package/dist/auth/error.mjs.map +1 -0
  34. package/dist/auth/index.cjs +249 -384
  35. package/dist/auth/index.cjs.map +1 -1
  36. package/dist/auth/index.d.cts +66 -55
  37. package/dist/auth/index.d.cts.map +1 -1
  38. package/dist/auth/index.d.mts +66 -55
  39. package/dist/auth/index.d.mts.map +1 -1
  40. package/dist/auth/index.mjs +252 -387
  41. package/dist/auth/index.mjs.map +1 -1
  42. package/dist/auth/respond.cjs +99 -0
  43. package/dist/auth/respond.cjs.map +1 -0
  44. package/dist/auth/respond.d.cts +39 -0
  45. package/dist/auth/respond.d.cts.map +1 -0
  46. package/dist/auth/respond.d.mts +39 -0
  47. package/dist/auth/respond.d.mts.map +1 -0
  48. package/dist/auth/respond.mjs +92 -0
  49. package/dist/auth/respond.mjs.map +1 -0
  50. package/dist/auth/types.d.cts +27 -11
  51. package/dist/auth/types.d.cts.map +1 -1
  52. package/dist/auth/types.d.mts +27 -11
  53. package/dist/auth/types.d.mts.map +1 -1
  54. package/dist/index.cjs +5 -0
  55. package/dist/index.d.cts +4 -1
  56. package/dist/index.d.mts +4 -1
  57. package/dist/index.mjs +3 -1
  58. package/dist/oauth2/error.cjs +0 -7
  59. package/dist/oauth2/error.cjs.map +1 -1
  60. package/dist/oauth2/error.d.cts +0 -3
  61. package/dist/oauth2/error.d.cts.map +1 -1
  62. package/dist/oauth2/error.d.mts +0 -3
  63. package/dist/oauth2/error.d.mts.map +1 -1
  64. package/dist/oauth2/error.mjs +1 -7
  65. package/dist/oauth2/error.mjs.map +1 -1
  66. package/dist/oauth2/provider/common.cjs +17 -3
  67. package/dist/oauth2/provider/common.cjs.map +1 -1
  68. package/dist/oauth2/provider/common.d.cts.map +1 -1
  69. package/dist/oauth2/provider/common.d.mts.map +1 -1
  70. package/dist/oauth2/provider/common.mjs +17 -3
  71. package/dist/oauth2/provider/common.mjs.map +1 -1
  72. package/dist/oauth2/provider/facebook.cjs +2 -4
  73. package/dist/oauth2/provider/facebook.cjs.map +1 -1
  74. package/dist/oauth2/provider/facebook.d.cts +0 -1
  75. package/dist/oauth2/provider/facebook.d.cts.map +1 -1
  76. package/dist/oauth2/provider/facebook.d.mts +0 -1
  77. package/dist/oauth2/provider/facebook.d.mts.map +1 -1
  78. package/dist/oauth2/provider/facebook.mjs +2 -4
  79. package/dist/oauth2/provider/facebook.mjs.map +1 -1
  80. package/dist/oauth2/provider/github.cjs +1 -1
  81. package/dist/oauth2/provider/github.cjs.map +1 -1
  82. package/dist/oauth2/provider/github.d.cts +1 -1
  83. package/dist/oauth2/provider/github.d.cts.map +1 -1
  84. package/dist/oauth2/provider/github.d.mts +1 -1
  85. package/dist/oauth2/provider/github.d.mts.map +1 -1
  86. package/dist/oauth2/provider/github.mjs +1 -1
  87. package/dist/oauth2/provider/github.mjs.map +1 -1
  88. package/dist/oauth2/provider/x.cjs +6 -4
  89. package/dist/oauth2/provider/x.cjs.map +1 -1
  90. package/dist/oauth2/provider/x.d.cts +3 -2
  91. package/dist/oauth2/provider/x.d.cts.map +1 -1
  92. package/dist/oauth2/provider/x.d.mts +3 -2
  93. package/dist/oauth2/provider/x.d.mts.map +1 -1
  94. package/dist/oauth2/provider/x.mjs +6 -4
  95. package/dist/oauth2/provider/x.mjs.map +1 -1
  96. package/dist/oauth2/types.cjs.map +1 -1
  97. package/dist/oauth2/types.d.cts +5 -0
  98. package/dist/oauth2/types.d.cts.map +1 -1
  99. package/dist/oauth2/types.d.mts +5 -0
  100. package/dist/oauth2/types.d.mts.map +1 -1
  101. package/dist/oauth2/types.mjs.map +1 -1
  102. package/dist/session/common.cjs +10 -13
  103. package/dist/session/common.cjs.map +1 -1
  104. package/dist/session/common.d.cts +7 -3
  105. package/dist/session/common.d.cts.map +1 -1
  106. package/dist/session/common.d.mts +7 -3
  107. package/dist/session/common.d.mts.map +1 -1
  108. package/dist/session/common.mjs +10 -12
  109. package/dist/session/common.mjs.map +1 -1
  110. package/dist/session/db.cjs +2 -2
  111. package/dist/session/db.cjs.map +1 -1
  112. package/dist/session/db.mjs +3 -3
  113. package/dist/session/db.mjs.map +1 -1
  114. package/dist/session/redis.cjs +6 -8
  115. package/dist/session/redis.cjs.map +1 -1
  116. package/dist/session/redis.d.cts.map +1 -1
  117. package/dist/session/redis.d.mts.map +1 -1
  118. package/dist/session/redis.mjs +7 -9
  119. package/dist/session/redis.mjs.map +1 -1
  120. package/dist/session/types.d.cts +0 -12
  121. package/dist/session/types.d.cts.map +1 -1
  122. package/dist/session/types.d.mts +0 -12
  123. package/dist/session/types.d.mts.map +1 -1
  124. package/dist/utils/valid.cjs +34 -52
  125. package/dist/utils/valid.cjs.map +1 -1
  126. package/dist/utils/valid.d.cts +8 -23
  127. package/dist/utils/valid.d.cts.map +1 -1
  128. package/dist/utils/valid.d.mts +8 -23
  129. package/dist/utils/valid.d.mts.map +1 -1
  130. package/dist/utils/valid.mjs +32 -50
  131. package/dist/utils/valid.mjs.map +1 -1
  132. package/package.json +3 -3
@@ -1,5 +1,6 @@
1
1
  import { Principal } from "../core/index.mjs";
2
2
  import { Session } from "../session/types.mjs";
3
+ import { EmailSender } from "./email-codes.mjs";
3
4
  import { AuthConfig, AuthService, AuthorizedHandler, LoggedHandler, OAuth2AuthorizationOptions } from "./types.mjs";
4
5
  export declare const PATH: {
5
6
  readonly CSRF: '/csrf';
@@ -12,13 +13,18 @@ export declare const PATH: {
12
13
  readonly LOGIN_OAUTH2_NATIVE: '/login/oauth2/native/:provider';
13
14
  readonly LOGIN_OAUTH2_ONETAP: '/login/oauth2/onetap/google';
14
15
  readonly LOGIN_EMAIL: '/login/email';
15
- readonly LOGIN_PHONE: '/login/phone';
16
16
  readonly SEND_EMAIL_VERIFICATION_CODE: '/auth/sendEmailVerificationCode';
17
- readonly SEND_PHONE_VERIFICATION_CODE: '/auth/sendPhoneVerificationCode';
18
17
  readonly DESKTOP_AUTHORIZE: '/auth/desktop/authorize';
19
18
  readonly DESKTOP_EXCHANGE: '/auth/desktop/exchange';
20
19
  readonly CLEANUP_EXPIRED_SESSIONS: '/sessions/expired/cleanup';
21
20
  };
21
+ /**
22
+ * The login routes and session helpers. Every route is one straight line: read and validate the
23
+ * request, check the credential, call the host's `onAuthorized`, sign in. Failures are thrown and
24
+ * answered in one place per kind of route: a JSON route runs under `guard`, which maps a
25
+ * `RequestError`, an `AuthenticationError` or an `OAuth2Error` to its google.rpc body (see
26
+ * `respond.ts`); the redirect callback maps the same errors to `errorUri` parameters (`redirect`).
27
+ */
22
28
  export declare class Auth implements AuthService {
23
29
  private readonly timing;
24
30
  private readonly cookieName;
@@ -26,87 +32,92 @@ export declare class Auth implements AuthService {
26
32
  private readonly kv;
27
33
  private readonly repository;
28
34
  private readonly turnstileSecretKey?;
29
- private readonly maxVerificationAttempts;
30
- private readonly verificationResendCooldown;
35
+ private readonly emailCodes;
36
+ private readonly desktopCodes;
31
37
  private readonly oauth2Client;
32
38
  private readonly onetapRequireNonce;
33
39
  private readonly touchInterval;
40
+ private readonly sessionHeader;
34
41
  private readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST;
35
42
  constructor({ sessionRepository, kvRepository, turnstileSecretKey, verificationCode, oauth2, cookie, session, timing }: AuthConfig);
36
43
  private getSessionId;
44
+ /** The session the request carries, if it exists. */
45
+ private loadSession;
46
+ /** The signed-in principal of a session, if any. */
47
+ private principalOf;
48
+ /**
49
+ * A new session signed in as `principal`. With `carryOver`, what the host kept in the pre-login
50
+ * session moves to the new one; the authorization request (state, PKCE verifier, flow
51
+ * attributes) is single-use and was consumed, and a principal left there from an earlier
52
+ * sign-in is replaced. The new id is never the one the browser brought, which may have been
53
+ * planted by an attacker who started the flow (session fixation).
54
+ */
55
+ private signIn;
56
+ /**
57
+ * `response` carrying `session`: its cookie, renewed for the whole inactive interval, and with
58
+ * `session.header` on, the `X-Auth-Token` header a client without a cookie jar reads.
59
+ */
60
+ private withSession;
61
+ /** Runs a JSON route: a failure it knows is its response, anything else is the host's. */
62
+ private guard;
63
+ /** The error redirect of the callback flow; `refusal`'s reason and metadata ride along. */
64
+ private redirect;
37
65
  csrf: (_request: Request) => Promise<Response>;
38
66
  logout: (request: Request) => Promise<Response>;
39
67
  logged: (request: Request, onLogged?: LoggedHandler) => Promise<Response>;
40
68
  private createPkceParameters;
41
69
  private getOauth2StateKey;
42
70
  private getOauth2NonceKey;
43
- private getEmailVerificationCodeKey;
44
- private getEmailVerificationAttemptsKey;
45
- private getEmailVerificationCooldownKey;
46
- private getPhoneVerificationCodeKey;
47
- private getDesktopCodeKey;
71
+ /**
72
+ * Spends a nonce `oauth2Nonce` issued for `provider`: `NONCE_MISMATCH` for one that was not,
73
+ * or was used already. An id token is replayable until it expires; the nonce it carries makes
74
+ * it sign in once.
75
+ */
76
+ private consumeNonce;
48
77
  oauth2State: (request: Request) => Promise<Response>;
49
78
  oauth2Nonce: (request: Request) => Promise<Response>;
50
79
  oauth2Authorization: (request: Request, options?: OAuth2AuthorizationOptions) => Promise<Response>;
51
- private readonly redirect;
52
80
  loginOAuth2Code: (request: Request, handler: AuthorizedHandler) => Promise<Response>;
53
- /**
54
- * A fresh session for `name`, carrying over what the host kept in the pre-login one. The
55
- * authorization request (state, PKCE verifier, flow attributes) is single-use and was consumed
56
- * by this callback, and a principal left there from an earlier sign-in is replaced.
57
- */
58
- private signedInSession;
81
+ /** The shared tail of the JSON login routes: the host's handler, then a signed-in session. */
82
+ private authorize;
59
83
  loginOAuth2Native: (request: Request, handler: AuthorizedHandler) => Promise<Response>;
60
84
  loginOAuth2Onetap: (request: Request, handler: AuthorizedHandler, provider?: string) => Promise<Response>;
61
- sendEmailVerificationCode: (request: Request, sender: (data: {
62
- email: string;
63
- verificationCode: string;
64
- }) => Promise<void>) => Promise<Response>;
85
+ sendEmailVerificationCode: (request: Request, sender: EmailSender) => Promise<Response>;
86
+ verifyEmailCode: (email: string, verificationCode: string) => Promise<void>;
65
87
  loginEmail: (request: Request, handler: AuthorizedHandler) => Promise<Response>;
66
88
  /**
67
- * Step 1 of the desktop / native app sign-in handshake. The caller already
68
- * completed normal sign-in on the web (so a cookie session exists); this
69
- * issues a 5-minute single-use code that the web page hands off to the
70
- * desktop client via a loopback redirect.
89
+ * Step 1 of the desktop / native app sign-in handshake. The caller already completed normal
90
+ * sign-in on the web (so a cookie session exists); this issues a 5-minute single-use code that
91
+ * the web page hands off to the desktop client via a loopback redirect.
71
92
  *
72
- * PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier`
73
- * before opening the browser, derives `code_challenge = base64url(sha256(verifier))`,
74
- * and threads the challenge through the URL → web page → here. We pin
75
- * the challenge to the code in KV; `desktopExchange` enforces that the
76
- * caller knows the matching verifier. This blocks an attacker who only
77
- * intercepts the auth code (browser history, malicious extension, packet
78
- * capture on loopback) from exchanging it.
93
+ * PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier` before opening the
94
+ * browser, derives `code_challenge = base64url(sha256(verifier))`, and threads the challenge
95
+ * through the URL → web page → here. The challenge is pinned to the code; `desktopExchange`
96
+ * enforces that the caller knows the matching verifier. This blocks an attacker who only
97
+ * intercepts the auth code (browser history, malicious extension, packet capture on loopback)
98
+ * from exchanging it.
79
99
  *
80
- * Requires an authenticated cookie session — anonymous callers get 401.
81
- * Throttling / abuse protection on this endpoint is the API layer's job
82
- * (rate-limit per principal); the SDK doesn't gate it because the
83
- * upstream sign-in flow already passed CAPTCHA / OAuth / verification.
100
+ * Requires an authenticated cookie session: anonymous callers get 401. Throttling / abuse
101
+ * protection on this endpoint is the API layer's job (rate-limit per principal); the SDK does
102
+ * not gate it because the upstream sign-in flow already passed CAPTCHA / OAuth / verification.
84
103
  */
85
104
  desktopAuthorize: (request: Request) => Promise<Response>;
86
105
  /**
87
- * Step 2 of the desktop / native app sign-in handshake. The desktop
88
- * client hands back the auth code it received via the loopback callback,
89
- * and we mint a fresh session for it — independent of the web cookie
90
- * session that originally `authorize`'d the code, so logging out on
91
- * either side doesn't cascade to the other.
106
+ * Step 2 of the desktop / native app sign-in handshake. The desktop client hands back the auth
107
+ * code it received via the loopback callback, and we mint a fresh session for it, independent
108
+ * of the web cookie session that originally authorized the code, so logging out on either side
109
+ * does not cascade to the other.
92
110
  *
93
- * Cookie attributes: defaults to `SameSite=None; Secure` so the cookie
94
- * survives cross-site fetches from the desktop renderer's `file://` /
95
- * `app://` origin. The configured base `cookieOptions` provide
96
- * everything else (`domain`, `path`, `httpOnly`, `maxAge`); the override
97
- * is targeted at the SameSite/Secure pair only. In a non-HTTPS dev
98
- * backend this combination is rejected by Chromium — see the desktop
99
- * sign-in PRD for workarounds.
111
+ * Cookie attributes: `SameSite=None; Secure` so the cookie survives cross-site fetches from the
112
+ * desktop renderer's `file://` / `app://` origin. The configured `cookieOptions` provide
113
+ * everything else (`domain`, `path`, `httpOnly`); the override is targeted at the
114
+ * SameSite/Secure pair only. In a non-HTTPS dev backend this combination is rejected by
115
+ * Chromium, see the desktop sign-in PRD for workarounds.
100
116
  *
101
- * No CSRF check needed — the request carries no cookie, the auth code
102
- * itself is the credential, and KV's single-use removal blocks replay.
117
+ * No CSRF check needed: the request carries no cookie, the auth code itself is the credential,
118
+ * and the code's single-use removal blocks replay.
103
119
  */
104
120
  desktopExchange: (request: Request) => Promise<Response>;
105
- sendPhoneVerificationCode: (_request: Request, _sender: (data: {
106
- phone: string;
107
- verificationCode: string;
108
- }) => Promise<void>) => Promise<Response>;
109
- loginPhone: (_request: Request, _handler: AuthorizedHandler) => Promise<Response>;
110
121
  kick: (principal: Principal) => Promise<void>;
111
122
  /**
112
123
  * Sliding expiration: an authenticated request renews the session, as `logged` does, so a user
@@ -116,10 +127,10 @@ export declare class Auth implements AuthService {
116
127
  * `AuthConfig`), so a session accessed again within the interval is not rewritten.
117
128
  */
118
129
  private touch;
130
+ getPrincipal: (request: Request) => Promise<Principal | null>;
119
131
  isAuthenticated: (request: Request) => Promise<boolean>;
120
132
  getSession: <T extends boolean>(request: Request, create: T) => Promise<T extends true ? Session : Session | null>;
121
133
  deleteSession: (sessionId: string) => Promise<void>;
122
- getPrincipal: (request: Request) => Promise<Principal | null>;
123
134
  listSessions: (principal: Principal) => Promise<Session[]>;
124
135
  cleanupExpiredSessions: (cleanupCount?: number) => Promise<Response>;
125
136
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/auth/index.ts"],"mappings":";;;qBA4Ga;WACX;WACA;WACA;WAEA;WACA;WACA;WACA;WACA;WACA;WAEA;WACA;WACA;WACA;WASA;WACA;WAEA;;qBAMW,gBAAgB;mBACV;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;EAEjB,cACE,mBACA,cACA,oBACA,kBACA,QACA,QACA,SACA,UACC;UA2BK;EAYR,OAAI,UAAoB,YAAU,QAAQ;EAmB1C,SAAM,SAAmB,YAAU,QAAQ;EAU3C,SAAM,SAAmB,SAAO,WAAa,kBAAgB,QAAQ;UAsB7D;UAUA;UAIA;UAIA;UAIA;UAIA;UAIA;UAIA;EAIR,cAAW,SAAmB,YAAU,QAAQ;EAOhD,cAAW,SAAmB,YAAU,QAAQ;EAOhD,sBAAmB,SACR,SAAO,UACN,+BACT,QAAQ;mBAmCM;EAUjB,kBAAe,SAAmB,SAAO,SAAW,sBAAoB,QAAQ;;;;;;UA+HxE;EAYR,oBAAiB,SAAmB,SAAO,SAAW,sBAAoB,QAAQ;EAmDlF,oBAAiB,SACN,SAAO,SACP,mBAAiB,sBAEzB,QAAQ;EA6EX,4BAAyB,SACd,SAAO,SACP;IAAQ;IAAe;QAA+B,kBAC9D,QAAQ;EA0CX,aAAU,SAAmB,SAAO,SAAW,sBAAoB,QAAQ;;;;;;;;;;;;;;;;;;;;EAsE3E,mBAAgB,SAAmB,YAAU,QAAQ;;;;;;;;;;;;;;;;;;;EA0DrD,kBAAe,SAAmB,YAAU,QAAQ;EAgEpD,4BAAyB,UACb,SAAO,UACP;IAAQ;IAAe;QAA+B,kBAC/D,QAAQ;EAIX,aAAU,UAAoB,SAAO,UAAY,sBAAoB,QAAQ;EAI7E,OAAI,WAAqB,cAAY;;;;;;;;UAcvB;EAOd,kBAAe,SAAmB,YAAU;EAW5C,aAAoB,mBAAiB,SAC1B,SAAO,QACR,MACP,QAAQ,iBAAiB,UAAU;EAwBtC,gBAAa,sBAA8B;EAI3C,eAAY,SAAmB,YAAU,QAAQ;EAWjD,eAAY,WAAqB,cAAY,QAAQ;EAKrD,yBAAsB,0BAAkC,QAAQ"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/auth/index.ts"],"mappings":";;;;qBAyHa;WACX;WACA;WACA;WAEA;WACA;WACA;WACA;WACA;WACA;WAEA;WACA;WASA;WACA;WAEA;;;;;;;;;qBAUW,gBAAgB;mBACV;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;EAEjB,cACE,mBACA,cACA,oBACA,kBACA,QACA,QACA,SACA,UACC;UA6BK;;UAOM;;UAMN;;;;;;;;UAYM;;;;;UAiBN;;UAYM;;UAWN;EAqBR,OAAI,UAAoB,YAAU,QAAQ;EAmB1C,SAAM,SAAmB,YAAU,QAAQ;EAU3C,SAAM,SAAmB,SAAO,WAAa,kBAAgB,QAAQ;UAc7D;UAWA;UAIA;;;;;;UASM;EAMd,cAAW,SAAmB,YAAU,QAAQ;EAOhD,cAAW,SAAmB,YAAU,QAAQ;EAOhD,sBAAmB,SACR,SAAO,UACN,+BACT,QAAQ;EAqCX,kBAAe,SAAmB,SAAO,SAAW,sBAAoB,QAAQ;;UAoGlE;EAad,oBAAiB,SAAa,SAAO,SAAW,sBAAoB,QAAQ;EAwC5E,oBAAiB,SACN,SAAO,SACP,mBAAiB,sBAEzB,QAAQ;EAqDX,4BAAyB,SAAa,SAAO,QAAU,gBAAc,QAAQ;EAY7E,kBAAe,eAAiB,6BAA6B;EAG7D,aAAU,SAAa,SAAO,SAAW,sBAAoB,QAAQ;;;;;;;;;;;;;;;;;EAoCrE,mBAAgB,SAAa,YAAU,QAAQ;;;;;;;;;;;;;;;;EAwB/C,kBAAe,SAAa,YAAU,QAAQ;EAe9C,OAAI,WAAqB,cAAY;;;;;;;;UAcvB;EAOd,eAAY,SAAmB,YAAU,QAAQ;EAQjD,kBAAe,SAAmB,YAAU;EAI5C,aAAoB,mBAAiB,SAC1B,SAAO,QACR,MACP,QAAQ,iBAAiB,UAAU;EAStC,gBAAa,sBAA8B;EAI3C,eAAY,WAAqB,cAAY,QAAQ;EAKrD,yBAAsB,0BAAkC,QAAQ"}