@shipfox/api-auth 10.2.0 → 11.0.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.
Files changed (35) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +30 -14
  4. package/dist/core/auth.d.ts.map +1 -1
  5. package/dist/core/auth.js +16 -8
  6. package/dist/core/auth.js.map +1 -1
  7. package/dist/core/jwt.d.ts +10 -0
  8. package/dist/core/jwt.d.ts.map +1 -1
  9. package/dist/core/jwt.js +3 -2
  10. package/dist/core/jwt.js.map +1 -1
  11. package/dist/db/refresh-tokens.d.ts +0 -4
  12. package/dist/db/refresh-tokens.d.ts.map +1 -1
  13. package/dist/db/refresh-tokens.js +0 -6
  14. package/dist/db/refresh-tokens.js.map +1 -1
  15. package/dist/presentation/auth/jwt-auth.d.ts +1 -4
  16. package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
  17. package/dist/presentation/auth/jwt-auth.js +5 -29
  18. package/dist/presentation/auth/jwt-auth.js.map +1 -1
  19. package/dist/tsconfig.test.tsbuildinfo +1 -1
  20. package/package.json +3 -3
  21. package/src/core/auth.test.ts +29 -10
  22. package/src/core/auth.ts +27 -12
  23. package/src/core/jwt.test.ts +20 -4
  24. package/src/core/jwt.ts +2 -1
  25. package/src/db/refresh-tokens.test.ts +0 -19
  26. package/src/db/refresh-tokens.ts +0 -23
  27. package/src/presentation/auth/jwt-auth.test.ts +33 -8
  28. package/src/presentation/auth/jwt-auth.ts +3 -26
  29. package/src/presentation/routes/administration.test.ts +9 -1
  30. package/src/presentation/routes/password/change-password.test.ts +6 -0
  31. package/src/presentation/routes/password/password-reset-confirm.test.ts +14 -0
  32. package/src/presentation/routes/registration/signup.test.ts +2 -2
  33. package/src/presentation/routes/session/logout.test.ts +6 -0
  34. package/src/presentation/routes/session/refresh.test.ts +11 -0
  35. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"jwt-auth.d.ts","sourceRoot":"","sources":["../../../src/presentation/auth/jwt-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAC,KAAK,UAAU,EAAc,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AASjD,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC;AAExC,MAAM,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;AAChC,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;AAEzD,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,gBAAgB,CAAC;CACpC,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,GAAG,IAAI,CAE9E;AAoBD;;;GAGG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,2BAA2B,CAAC,CAatC;AAED,wBAAgB,mBAAmB,IAAI,UAAU,CAqChD"}
1
+ {"version":3,"file":"jwt-auth.d.ts","sourceRoot":"","sources":["../../../src/presentation/auth/jwt-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAC,KAAK,UAAU,EAAc,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAOjD,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC;AAExC,MAAM,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;AAChC,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;AAEzD,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,gBAAgB,CAAC;CACpC,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,GAAG,IAAI,CAE9E;AAoBD,+EAA+E;AAC/E,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,2BAA2B,CAAC,CAOtC;AAED,wBAAgB,mBAAmB,IAAI,UAAU,CAyBhD"}
@@ -2,8 +2,6 @@ import { AUTH_USER, buildUserContext, getUserContext, setUserContext } from '@sh
2
2
  import { userAccessTokenKey } from '@shipfox/node-auth-root-key';
3
3
  import { ClientError } from '@shipfox/node-fastify';
4
4
  import { verifyUserToken } from '#core/jwt.js';
5
- import { findActiveRefreshSession } from '#db/refresh-tokens.js';
6
- import { findUserById } from '#db/users.js';
7
5
  import { createBearerTokenAuthMethod } from './bearer-token-auth.js';
8
6
  const AUTHENTICATED_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth/session');
9
7
  let InvalidJwtTokenError = class InvalidJwtTokenError extends Error {
@@ -24,26 +22,16 @@ function setAuthenticatedSessionContext(request, claims) {
24
22
  function getAuthenticatedSessionTokenContext(request) {
25
23
  return request[AUTHENTICATED_SESSION_CONTEXT_KEY] ?? null;
26
24
  }
27
- /**
28
- * Resolves the active refresh session backing an authenticated user request.
29
- * The session check intentionally happens only for consumers that need browser-session binding.
30
- */ export async function getAuthenticatedSessionContext(request) {
25
+ /** Returns the refresh-session metadata carried by a verified access token. */ export async function getAuthenticatedSessionContext(request) {
31
26
  const client = getClientContext(request);
32
27
  const token = getAuthenticatedSessionTokenContext(request);
33
28
  if (!client || !token || token.userId !== client.userId || !token.refreshSessionId) throw new ClientError('Invalid or expired token', 'unauthorized', {
34
29
  status: 401
35
30
  });
36
- const session = await findActiveRefreshSession({
37
- sessionId: token.refreshSessionId,
38
- userId: client.userId
39
- });
40
- if (!session) throw new ClientError('Invalid or expired token', 'unauthorized', {
41
- status: 401
42
- });
43
- return {
31
+ return await Promise.resolve({
44
32
  userId: client.userId,
45
- refreshSessionId: session.sessionId
46
- };
33
+ refreshSessionId: token.refreshSessionId
34
+ });
47
35
  }
48
36
  export function createJwtAuthMethod() {
49
37
  return createBearerTokenAuthMethod({
@@ -58,19 +46,7 @@ export function createJwtAuthMethod() {
58
46
  } catch {
59
47
  throw new InvalidJwtTokenError();
60
48
  }
61
- const user = await findUserById({
62
- id: claims.sub
63
- });
64
- // Legacy access tokens without a session claim remain verifiable for the
65
- // migration window; known suspended or deleted users are still rejected.
66
- if (user && user.status !== 'active') return null;
67
- if (!claims.refreshSessionId) return claims;
68
- if (!user) return null;
69
- const session = await findActiveRefreshSession({
70
- sessionId: claims.refreshSessionId,
71
- userId: claims.sub
72
- });
73
- return session ? claims : null;
49
+ return claims;
74
50
  },
75
51
  isInvalidTokenError: (error)=>error instanceof InvalidJwtTokenError,
76
52
  invalidTokenError: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/auth/jwt-auth.ts"],"sourcesContent":["import {\n AUTH_USER,\n buildUserContext,\n getUserContext,\n setUserContext,\n type UserContext,\n} from '@shipfox/api-auth-context';\nimport {userAccessTokenKey} from '@shipfox/node-auth-root-key';\nimport {type AuthMethod, ClientError} from '@shipfox/node-fastify';\nimport type {FastifyRequest} from 'fastify';\nimport type {RefreshToken} from '#core/entities/refresh-token.js';\nimport type {User} from '#core/entities/user.js';\nimport type {UserTokenClaims} from '#core/jwt.js';\nimport {verifyUserToken} from '#core/jwt.js';\nimport {findActiveRefreshSession} from '#db/refresh-tokens.js';\nimport {findUserById} from '#db/users.js';\nimport {createBearerTokenAuthMethod} from './bearer-token-auth.js';\n\nconst AUTHENTICATED_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth/session');\n\nexport type ClientContext = UserContext;\n\nexport type UserId = User['id'];\nexport type RefreshSessionId = RefreshToken['sessionId'];\n\nexport type AuthenticatedSessionContext = {\n userId: UserId;\n refreshSessionId: RefreshSessionId;\n};\n\nexport interface CreateJwtAuthMethodOptions {\n secret: string;\n}\n\nclass InvalidJwtTokenError extends Error {\n constructor() {\n super('Invalid JWT');\n this.name = 'InvalidJwtTokenError';\n }\n}\n\nexport function getClientContext(request: FastifyRequest): ClientContext | null {\n return getUserContext(request);\n}\n\nfunction setAuthenticatedSessionContext(request: FastifyRequest, claims: UserTokenClaims): void {\n (request as unknown as Record<symbol, unknown>)[AUTHENTICATED_SESSION_CONTEXT_KEY] = {\n userId: claims.sub,\n refreshSessionId: claims.refreshSessionId,\n };\n}\n\nfunction getAuthenticatedSessionTokenContext(request: FastifyRequest): {\n userId: UserId;\n refreshSessionId: RefreshSessionId | undefined;\n} | null {\n return (\n ((request as unknown as Record<symbol, unknown>)[AUTHENTICATED_SESSION_CONTEXT_KEY] as\n | {userId: UserId; refreshSessionId: RefreshSessionId | undefined}\n | undefined) ?? null\n );\n}\n\n/**\n * Resolves the active refresh session backing an authenticated user request.\n * The session check intentionally happens only for consumers that need browser-session binding.\n */\nexport async function getAuthenticatedSessionContext(\n request: FastifyRequest,\n): Promise<AuthenticatedSessionContext> {\n const client = getClientContext(request);\n const token = getAuthenticatedSessionTokenContext(request);\n if (!client || !token || token.userId !== client.userId || !token.refreshSessionId)\n throw new ClientError('Invalid or expired token', 'unauthorized', {status: 401});\n\n const session = await findActiveRefreshSession({\n sessionId: token.refreshSessionId,\n userId: client.userId,\n });\n if (!session) throw new ClientError('Invalid or expired token', 'unauthorized', {status: 401});\n\n return {userId: client.userId, refreshSessionId: session.sessionId};\n}\n\nexport function createJwtAuthMethod(): AuthMethod {\n return createBearerTokenAuthMethod({\n name: AUTH_USER,\n verifyToken: async (token) => {\n let claims: UserTokenClaims;\n try {\n claims = await verifyUserToken({token, secret: userAccessTokenKey()});\n } catch {\n throw new InvalidJwtTokenError();\n }\n const user = await findUserById({id: claims.sub});\n\n // Legacy access tokens without a session claim remain verifiable for the\n // migration window; known suspended or deleted users are still rejected.\n if (user && user.status !== 'active') return null;\n if (!claims.refreshSessionId) return claims;\n if (!user) return null;\n\n const session = await findActiveRefreshSession({\n sessionId: claims.refreshSessionId,\n userId: claims.sub,\n });\n return session ? claims : null;\n },\n isInvalidTokenError: (error) => error instanceof InvalidJwtTokenError,\n invalidTokenError: {message: 'Invalid or expired token', code: 'unauthorized'},\n setContext: (request, claims) => {\n const clientContext: ClientContext = buildUserContext({\n userId: claims.sub,\n email: claims.email,\n name: claims.name ?? null,\n memberships: claims.memberships,\n });\n setUserContext(request, clientContext);\n setAuthenticatedSessionContext(request, claims);\n },\n });\n}\n"],"names":["AUTH_USER","buildUserContext","getUserContext","setUserContext","userAccessTokenKey","ClientError","verifyUserToken","findActiveRefreshSession","findUserById","createBearerTokenAuthMethod","AUTHENTICATED_SESSION_CONTEXT_KEY","Symbol","for","InvalidJwtTokenError","Error","name","getClientContext","request","setAuthenticatedSessionContext","claims","userId","sub","refreshSessionId","getAuthenticatedSessionTokenContext","getAuthenticatedSessionContext","client","token","status","session","sessionId","createJwtAuthMethod","verifyToken","secret","user","id","isInvalidTokenError","error","invalidTokenError","message","code","setContext","clientContext","email","memberships"],"mappings":"AAAA,SACEA,SAAS,EACTC,gBAAgB,EAChBC,cAAc,EACdC,cAAc,QAET,4BAA4B;AACnC,SAAQC,kBAAkB,QAAO,8BAA8B;AAC/D,SAAyBC,WAAW,QAAO,wBAAwB;AAKnE,SAAQC,eAAe,QAAO,eAAe;AAC7C,SAAQC,wBAAwB,QAAO,wBAAwB;AAC/D,SAAQC,YAAY,QAAO,eAAe;AAC1C,SAAQC,2BAA2B,QAAO,yBAAyB;AAEnE,MAAMC,oCAAoCC,OAAOC,GAAG,CAAC;AAgBrD,IAAA,AAAMC,uBAAN,MAAMA,6BAA6BC;IACjC,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,SAASC,iBAAiBC,OAAuB;IACtD,OAAOf,eAAee;AACxB;AAEA,SAASC,+BAA+BD,OAAuB,EAAEE,MAAuB;IACrFF,OAA8C,CAACP,kCAAkC,GAAG;QACnFU,QAAQD,OAAOE,GAAG;QAClBC,kBAAkBH,OAAOG,gBAAgB;IAC3C;AACF;AAEA,SAASC,oCAAoCN,OAAuB;IAIlE,OACE,AAAC,AAACA,OAA8C,CAACP,kCAAkC,IAEjE;AAEtB;AAEA;;;CAGC,GACD,OAAO,eAAec,+BACpBP,OAAuB;IAEvB,MAAMQ,SAAST,iBAAiBC;IAChC,MAAMS,QAAQH,oCAAoCN;IAClD,IAAI,CAACQ,UAAU,CAACC,SAASA,MAAMN,MAAM,KAAKK,OAAOL,MAAM,IAAI,CAACM,MAAMJ,gBAAgB,EAChF,MAAM,IAAIjB,YAAY,4BAA4B,gBAAgB;QAACsB,QAAQ;IAAG;IAEhF,MAAMC,UAAU,MAAMrB,yBAAyB;QAC7CsB,WAAWH,MAAMJ,gBAAgB;QACjCF,QAAQK,OAAOL,MAAM;IACvB;IACA,IAAI,CAACQ,SAAS,MAAM,IAAIvB,YAAY,4BAA4B,gBAAgB;QAACsB,QAAQ;IAAG;IAE5F,OAAO;QAACP,QAAQK,OAAOL,MAAM;QAAEE,kBAAkBM,QAAQC,SAAS;IAAA;AACpE;AAEA,OAAO,SAASC;IACd,OAAOrB,4BAA4B;QACjCM,MAAMf;QACN+B,aAAa,OAAOL;YAClB,IAAIP;YACJ,IAAI;gBACFA,SAAS,MAAMb,gBAAgB;oBAACoB;oBAAOM,QAAQ5B;gBAAoB;YACrE,EAAE,OAAM;gBACN,MAAM,IAAIS;YACZ;YACA,MAAMoB,OAAO,MAAMzB,aAAa;gBAAC0B,IAAIf,OAAOE,GAAG;YAAA;YAE/C,yEAAyE;YACzE,yEAAyE;YACzE,IAAIY,QAAQA,KAAKN,MAAM,KAAK,UAAU,OAAO;YAC7C,IAAI,CAACR,OAAOG,gBAAgB,EAAE,OAAOH;YACrC,IAAI,CAACc,MAAM,OAAO;YAElB,MAAML,UAAU,MAAMrB,yBAAyB;gBAC7CsB,WAAWV,OAAOG,gBAAgB;gBAClCF,QAAQD,OAAOE,GAAG;YACpB;YACA,OAAOO,UAAUT,SAAS;QAC5B;QACAgB,qBAAqB,CAACC,QAAUA,iBAAiBvB;QACjDwB,mBAAmB;YAACC,SAAS;YAA4BC,MAAM;QAAc;QAC7EC,YAAY,CAACvB,SAASE;YACpB,MAAMsB,gBAA+BxC,iBAAiB;gBACpDmB,QAAQD,OAAOE,GAAG;gBAClBqB,OAAOvB,OAAOuB,KAAK;gBACnB3B,MAAMI,OAAOJ,IAAI,IAAI;gBACrB4B,aAAaxB,OAAOwB,WAAW;YACjC;YACAxC,eAAec,SAASwB;YACxBvB,+BAA+BD,SAASE;QAC1C;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/presentation/auth/jwt-auth.ts"],"sourcesContent":["import {\n AUTH_USER,\n buildUserContext,\n getUserContext,\n setUserContext,\n type UserContext,\n} from '@shipfox/api-auth-context';\nimport {userAccessTokenKey} from '@shipfox/node-auth-root-key';\nimport {type AuthMethod, ClientError} from '@shipfox/node-fastify';\nimport type {FastifyRequest} from 'fastify';\nimport type {RefreshToken} from '#core/entities/refresh-token.js';\nimport type {User} from '#core/entities/user.js';\nimport type {UserTokenClaims} from '#core/jwt.js';\nimport {verifyUserToken} from '#core/jwt.js';\nimport {createBearerTokenAuthMethod} from './bearer-token-auth.js';\n\nconst AUTHENTICATED_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth/session');\n\nexport type ClientContext = UserContext;\n\nexport type UserId = User['id'];\nexport type RefreshSessionId = RefreshToken['sessionId'];\n\nexport type AuthenticatedSessionContext = {\n userId: UserId;\n refreshSessionId: RefreshSessionId;\n};\n\nexport interface CreateJwtAuthMethodOptions {\n secret: string;\n}\n\nclass InvalidJwtTokenError extends Error {\n constructor() {\n super('Invalid JWT');\n this.name = 'InvalidJwtTokenError';\n }\n}\n\nexport function getClientContext(request: FastifyRequest): ClientContext | null {\n return getUserContext(request);\n}\n\nfunction setAuthenticatedSessionContext(request: FastifyRequest, claims: UserTokenClaims): void {\n (request as unknown as Record<symbol, unknown>)[AUTHENTICATED_SESSION_CONTEXT_KEY] = {\n userId: claims.sub,\n refreshSessionId: claims.refreshSessionId,\n };\n}\n\nfunction getAuthenticatedSessionTokenContext(request: FastifyRequest): {\n userId: UserId;\n refreshSessionId: RefreshSessionId | undefined;\n} | null {\n return (\n ((request as unknown as Record<symbol, unknown>)[AUTHENTICATED_SESSION_CONTEXT_KEY] as\n | {userId: UserId; refreshSessionId: RefreshSessionId | undefined}\n | undefined) ?? null\n );\n}\n\n/** Returns the refresh-session metadata carried by a verified access token. */\nexport async function getAuthenticatedSessionContext(\n request: FastifyRequest,\n): Promise<AuthenticatedSessionContext> {\n const client = getClientContext(request);\n const token = getAuthenticatedSessionTokenContext(request);\n if (!client || !token || token.userId !== client.userId || !token.refreshSessionId)\n throw new ClientError('Invalid or expired token', 'unauthorized', {status: 401});\n\n return await Promise.resolve({userId: client.userId, refreshSessionId: token.refreshSessionId});\n}\n\nexport function createJwtAuthMethod(): AuthMethod {\n return createBearerTokenAuthMethod({\n name: AUTH_USER,\n verifyToken: async (token) => {\n let claims: UserTokenClaims;\n try {\n claims = await verifyUserToken({token, secret: userAccessTokenKey()});\n } catch {\n throw new InvalidJwtTokenError();\n }\n return claims;\n },\n isInvalidTokenError: (error) => error instanceof InvalidJwtTokenError,\n invalidTokenError: {message: 'Invalid or expired token', code: 'unauthorized'},\n setContext: (request, claims) => {\n const clientContext: ClientContext = buildUserContext({\n userId: claims.sub,\n email: claims.email,\n name: claims.name ?? null,\n memberships: claims.memberships,\n });\n setUserContext(request, clientContext);\n setAuthenticatedSessionContext(request, claims);\n },\n });\n}\n"],"names":["AUTH_USER","buildUserContext","getUserContext","setUserContext","userAccessTokenKey","ClientError","verifyUserToken","createBearerTokenAuthMethod","AUTHENTICATED_SESSION_CONTEXT_KEY","Symbol","for","InvalidJwtTokenError","Error","name","getClientContext","request","setAuthenticatedSessionContext","claims","userId","sub","refreshSessionId","getAuthenticatedSessionTokenContext","getAuthenticatedSessionContext","client","token","status","Promise","resolve","createJwtAuthMethod","verifyToken","secret","isInvalidTokenError","error","invalidTokenError","message","code","setContext","clientContext","email","memberships"],"mappings":"AAAA,SACEA,SAAS,EACTC,gBAAgB,EAChBC,cAAc,EACdC,cAAc,QAET,4BAA4B;AACnC,SAAQC,kBAAkB,QAAO,8BAA8B;AAC/D,SAAyBC,WAAW,QAAO,wBAAwB;AAKnE,SAAQC,eAAe,QAAO,eAAe;AAC7C,SAAQC,2BAA2B,QAAO,yBAAyB;AAEnE,MAAMC,oCAAoCC,OAAOC,GAAG,CAAC;AAgBrD,IAAA,AAAMC,uBAAN,MAAMA,6BAA6BC;IACjC,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,SAASC,iBAAiBC,OAAuB;IACtD,OAAOb,eAAea;AACxB;AAEA,SAASC,+BAA+BD,OAAuB,EAAEE,MAAuB;IACrFF,OAA8C,CAACP,kCAAkC,GAAG;QACnFU,QAAQD,OAAOE,GAAG;QAClBC,kBAAkBH,OAAOG,gBAAgB;IAC3C;AACF;AAEA,SAASC,oCAAoCN,OAAuB;IAIlE,OACE,AAAC,AAACA,OAA8C,CAACP,kCAAkC,IAEjE;AAEtB;AAEA,6EAA6E,GAC7E,OAAO,eAAec,+BACpBP,OAAuB;IAEvB,MAAMQ,SAAST,iBAAiBC;IAChC,MAAMS,QAAQH,oCAAoCN;IAClD,IAAI,CAACQ,UAAU,CAACC,SAASA,MAAMN,MAAM,KAAKK,OAAOL,MAAM,IAAI,CAACM,MAAMJ,gBAAgB,EAChF,MAAM,IAAIf,YAAY,4BAA4B,gBAAgB;QAACoB,QAAQ;IAAG;IAEhF,OAAO,MAAMC,QAAQC,OAAO,CAAC;QAACT,QAAQK,OAAOL,MAAM;QAAEE,kBAAkBI,MAAMJ,gBAAgB;IAAA;AAC/F;AAEA,OAAO,SAASQ;IACd,OAAOrB,4BAA4B;QACjCM,MAAMb;QACN6B,aAAa,OAAOL;YAClB,IAAIP;YACJ,IAAI;gBACFA,SAAS,MAAMX,gBAAgB;oBAACkB;oBAAOM,QAAQ1B;gBAAoB;YACrE,EAAE,OAAM;gBACN,MAAM,IAAIO;YACZ;YACA,OAAOM;QACT;QACAc,qBAAqB,CAACC,QAAUA,iBAAiBrB;QACjDsB,mBAAmB;YAACC,SAAS;YAA4BC,MAAM;QAAc;QAC7EC,YAAY,CAACrB,SAASE;YACpB,MAAMoB,gBAA+BpC,iBAAiB;gBACpDiB,QAAQD,OAAOE,GAAG;gBAClBmB,OAAOrB,OAAOqB,KAAK;gBACnBzB,MAAMI,OAAOJ,IAAI,IAAI;gBACrB0B,aAAatB,OAAOsB,WAAW;YACjC;YACApC,eAAeY,SAASsB;YACxBrB,+BAA+BD,SAASE;QAC1C;IACF;AACF"}