@webda/auth 0.0.0-stage → 4.0.0-beta.4

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 (59) hide show
  1. package/LICENSE +166 -0
  2. package/README.md +26 -2
  3. package/lib/authentication.service.d.ts +286 -0
  4. package/lib/authentication.service.d.ts.map +1 -0
  5. package/lib/authentication.service.js +798 -0
  6. package/lib/authentication.service.js.map +1 -0
  7. package/lib/compat/upgrade.d.ts +71 -0
  8. package/lib/compat/upgrade.d.ts.map +1 -0
  9. package/lib/compat/upgrade.js +185 -0
  10. package/lib/compat/upgrade.js.map +1 -0
  11. package/lib/email/emailpassword.service.d.ts +241 -0
  12. package/lib/email/emailpassword.service.d.ts.map +1 -0
  13. package/lib/email/emailpassword.service.js +718 -0
  14. package/lib/email/emailpassword.service.js.map +1 -0
  15. package/lib/email/tokens.d.ts +36 -0
  16. package/lib/email/tokens.d.ts.map +1 -0
  17. package/lib/email/tokens.js +46 -0
  18. package/lib/email/tokens.js.map +1 -0
  19. package/lib/errors.d.ts +53 -0
  20. package/lib/errors.d.ts.map +1 -0
  21. package/lib/errors.js +73 -0
  22. package/lib/errors.js.map +1 -0
  23. package/lib/index.d.ts +9 -0
  24. package/lib/index.d.ts.map +1 -0
  25. package/lib/index.js +9 -0
  26. package/lib/index.js.map +1 -0
  27. package/lib/oauth/oauth.service.d.ts +293 -0
  28. package/lib/oauth/oauth.service.d.ts.map +1 -0
  29. package/lib/oauth/oauth.service.js +557 -0
  30. package/lib/oauth/oauth.service.js.map +1 -0
  31. package/lib/provider.d.ts +53 -0
  32. package/lib/provider.d.ts.map +1 -0
  33. package/lib/provider.js +55 -0
  34. package/lib/provider.js.map +1 -0
  35. package/lib/test/authtest.d.ts +39 -0
  36. package/lib/test/authtest.d.ts.map +1 -0
  37. package/lib/test/authtest.js +62 -0
  38. package/lib/test/authtest.js.map +1 -0
  39. package/lib/test/emailtest.d.ts +25 -0
  40. package/lib/test/emailtest.d.ts.map +1 -0
  41. package/lib/test/emailtest.js +39 -0
  42. package/lib/test/emailtest.js.map +1 -0
  43. package/lib/test/fakeoauth.service.d.ts +45 -0
  44. package/lib/test/fakeoauth.service.d.ts.map +1 -0
  45. package/lib/test/fakeoauth.service.js +78 -0
  46. package/lib/test/fakeoauth.service.js.map +1 -0
  47. package/lib/test/stubprovider.js +37 -0
  48. package/lib/test/stubprovider.js.map +1 -0
  49. package/lib/test/v3.d.ts +29 -0
  50. package/lib/test/v3.d.ts.map +1 -0
  51. package/lib/test/v3.js +47 -0
  52. package/lib/test/v3.js.map +1 -0
  53. package/lib/throttle.d.ts +40 -0
  54. package/lib/throttle.d.ts.map +1 -0
  55. package/lib/throttle.js +47 -0
  56. package/lib/throttle.js.map +1 -0
  57. package/package.json +65 -3
  58. package/test/config.json +20 -0
  59. package/webda.module.json +636 -0
@@ -0,0 +1,55 @@
1
+ import { useDynamicService } from "@webda/core";
2
+ import { EmailDomainNotAllowed } from "./errors.js";
3
+ /**
4
+ * @param email - an email address
5
+ * @returns the lowercased part after the last "@", undefined if none
6
+ */
7
+ export function emailDomain(email) {
8
+ const idx = email.lastIndexOf("@");
9
+ return idx < 0
10
+ ? undefined
11
+ : email
12
+ .substring(idx + 1)
13
+ .trim()
14
+ .toLowerCase();
15
+ }
16
+ /**
17
+ * Apply a provider email policy to a resolved identity without mutating it
18
+ * @param identity - identity asserted by the provider
19
+ * @param policy - the provider policy, if any
20
+ * @returns the identity to use (a copy with emailVerified forced to false when the claim is not trusted)
21
+ * @throws EmailDomainNotAllowed when the email is missing or its domain is not allowed
22
+ */
23
+ export function applyEmailPolicy(identity, policy) {
24
+ if (!policy)
25
+ return identity;
26
+ const domain = identity.email ? emailDomain(identity.email) : undefined;
27
+ const norm = (list) => list.map(d => d.trim().toLowerCase());
28
+ const trust = policy.trustEmailVerification;
29
+ let out = identity;
30
+ if (identity.emailVerified && trust !== undefined && trust !== true) {
31
+ if (trust === false || !domain || !norm(trust).includes(domain)) {
32
+ out = { ...identity, emailVerified: false };
33
+ }
34
+ }
35
+ // The allow-list only admits a present, listed and (still) verified email
36
+ if (policy.allowedEmailDomains &&
37
+ (!domain || !out.emailVerified || !norm(policy.allowedEmailDomains).includes(domain))) {
38
+ throw new EmailDomainNotAllowed();
39
+ }
40
+ return out;
41
+ }
42
+ /**
43
+ * @param service - any service
44
+ * @returns true when it implements AuthProvider
45
+ */
46
+ export function isAuthProvider(service) {
47
+ return !!service && typeof service.providerName === "string" && typeof service.getPublicInfo === "function";
48
+ }
49
+ /**
50
+ * @returns the Authentication service
51
+ */
52
+ export function useAuthentication() {
53
+ return useDynamicService("Authentication");
54
+ }
55
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0D,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAExG,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAgCpD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,OAAO,GAAG,GAAG,CAAC;QACZ,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,KAAK;aACF,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC;aAClB,IAAI,EAAE;aACN,WAAW,EAAE,CAAC;AACvB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAA0B,EAC1B,MAAuC;IAEvC,IAAI,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAC7B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACxE,MAAM,IAAI,GAAG,CAAC,IAAc,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACvE,MAAM,KAAK,GAAG,MAAM,CAAC,sBAAsB,CAAC;IAC5C,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,IAAI,QAAQ,CAAC,aAAa,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACpE,IAAI,KAAK,KAAK,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAChE,GAAG,GAAG,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,0EAA0E;IAC1E,IACE,MAAM,CAAC,mBAAmB;QAC1B,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EACrF,CAAC;QACD,MAAM,IAAI,qBAAqB,EAAE,CAAC;IACpC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,OAAY;IACzC,OAAO,CAAC,CAAC,OAAO,IAAI,OAAO,OAAO,CAAC,YAAY,KAAK,QAAQ,IAAI,OAAO,OAAO,CAAC,aAAa,KAAK,UAAU,CAAC;AAC9G,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,iBAAiB,CAAiB,gBAAgB,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { WebContext } from "@webda/core";
2
+ import { WebdaApplicationTest } from "@webda/core/lib/test/application.js";
3
+ import { DebugMailer } from "@webda/core/lib/services/debugmailer.service.js";
4
+ /**
5
+ * Base class for @webda/auth specs
6
+ */
7
+ export declare abstract class AuthTest extends WebdaApplicationTest {
8
+ mailer: DebugMailer;
9
+ /** @override */
10
+ beforeEach(): Promise<void>;
11
+ /**
12
+ * @param body - request body
13
+ * @returns a fresh web context with a session
14
+ */
15
+ ctx(body?: any): Promise<WebContext>;
16
+ /**
17
+ * Run code inside a context
18
+ * @param ctx - context
19
+ * @param fn - code
20
+ * @returns fn result
21
+ */
22
+ inContext<T>(ctx: WebContext, fn: () => Promise<T>): Promise<T>;
23
+ /**
24
+ * Call an operation by id and parse its JSON output.
25
+ *
26
+ * Input is passed as the request body: a fresh `WebContext` is created for every call (the
27
+ * input and the output are cached per context) and carries the session of `ctx`. The session
28
+ * is copied back to `ctx` afterwards so a login in one call is visible in the next one.
29
+ * Operations with an input schema receive the body fields as positional arguments, others
30
+ * receive the body object as their single argument.
31
+ * @param operationId - e.g. "Auth.Email.Login"
32
+ * @param input - operation input (request body)
33
+ * @param ctx - context to reuse (keeps the session between calls)
34
+ * @param headers - request headers, a JSON request by default
35
+ * @returns parsed output
36
+ */
37
+ op<T = any>(operationId: string, input?: any, ctx?: WebContext, headers?: Record<string, string>): Promise<T>;
38
+ }
39
+ //# sourceMappingURL=authtest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authtest.d.ts","sourceRoot":"","sources":["../../src/test/authtest.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,UAAU,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,iDAAiD,CAAC;AAE9E;;GAEG;AACH,8BAAsB,QAAS,SAAQ,oBAAoB;IACzD,MAAM,EAAE,WAAW,CAAC;IAEpB,gBAAgB;IACV,UAAU,kBAIf;IAED;;;OAGG;IACG,GAAG,CAAC,IAAI,GAAE,GAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAI7C;IAED;;;;;OAKG;IACH,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAE9D;IAED;;;;;;;;;;;;;OAaG;IACG,EAAE,CAAC,CAAC,GAAG,GAAG,EACd,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,GAAQ,EACf,GAAG,CAAC,EAAE,UAAU,EAChB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAA0C,GACvE,OAAO,CAAC,CAAC,CAAC,CAaZ;CACF"}
@@ -0,0 +1,62 @@
1
+ import { callOperation, HttpContext, runWithContext } from "@webda/core";
2
+ import { WebdaApplicationTest } from "@webda/core/lib/test/application.js";
3
+ /**
4
+ * Base class for @webda/auth specs
5
+ */
6
+ export class AuthTest extends WebdaApplicationTest {
7
+ /** @override */
8
+ async beforeEach() {
9
+ await super.beforeEach();
10
+ this.mailer = this.getService("DefinedMailer");
11
+ if (this.mailer)
12
+ this.mailer.sent = [];
13
+ }
14
+ /**
15
+ * @param body - request body
16
+ * @returns a fresh web context with a session
17
+ */
18
+ async ctx(body = {}) {
19
+ const ctx = await this.newContext(body);
20
+ ctx.newSession();
21
+ return ctx;
22
+ }
23
+ /**
24
+ * Run code inside a context
25
+ * @param ctx - context
26
+ * @param fn - code
27
+ * @returns fn result
28
+ */
29
+ inContext(ctx, fn) {
30
+ return runWithContext(ctx, fn);
31
+ }
32
+ /**
33
+ * Call an operation by id and parse its JSON output.
34
+ *
35
+ * Input is passed as the request body: a fresh `WebContext` is created for every call (the
36
+ * input and the output are cached per context) and carries the session of `ctx`. The session
37
+ * is copied back to `ctx` afterwards so a login in one call is visible in the next one.
38
+ * Operations with an input schema receive the body fields as positional arguments, others
39
+ * receive the body object as their single argument.
40
+ * @param operationId - e.g. "Auth.Email.Login"
41
+ * @param input - operation input (request body)
42
+ * @param ctx - context to reuse (keeps the session between calls)
43
+ * @param headers - request headers, a JSON request by default
44
+ * @returns parsed output
45
+ */
46
+ async op(operationId, input = {}, ctx, headers = { "content-type": "application/json" }) {
47
+ ctx ?? (ctx = await this.ctx());
48
+ const http = new HttpContext("test.webda.io", "POST", "/", "http", 80, headers);
49
+ http.setBody(input);
50
+ const call = await this.newWebContext(http);
51
+ call.setSession(ctx.getSession());
52
+ try {
53
+ await runWithContext(call, () => callOperation(call, operationId));
54
+ }
55
+ finally {
56
+ ctx.setSession(call.getSession());
57
+ }
58
+ const body = call.getResponseBody();
59
+ return body ? JSON.parse(body.toString()) : undefined;
60
+ }
61
+ }
62
+ //# sourceMappingURL=authtest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authtest.js","sourceRoot":"","sources":["../../src/test/authtest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAc,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAG3E;;GAEG;AACH,MAAM,OAAgB,QAAS,SAAQ,oBAAoB;IAGzD,gBAAgB;IAChB,KAAK,CAAC,UAAU;QACd,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAM,eAAe,CAAgB,CAAC;QACnE,IAAI,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAC,IAAI,GAAQ,EAAE;QACtB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAa,IAAI,CAAC,CAAC;QACpD,GAAG,CAAC,UAAU,EAAE,CAAC;QACjB,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAI,GAAe,EAAE,EAAoB;QAChD,OAAO,cAAc,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACjC,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,EAAE,CACN,WAAmB,EACnB,KAAK,GAAQ,EAAE,EACf,GAAgB,EAChB,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAExE,GAAG,KAAH,GAAG,GAAK,MAAM,IAAI,CAAC,GAAG,EAAE,EAAC;QACzB,MAAM,IAAI,GAAG,IAAI,WAAW,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;QAChF,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,aAAa,CAAa,IAAI,CAAC,CAAC;QACxD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;QACrE,CAAC;gBAAS,CAAC;YACT,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACxD,CAAC;CACF"}
@@ -0,0 +1,25 @@
1
+ import type { TestApplication } from "@webda/core/lib/test/objects.js";
2
+ import { AuthTest } from "./authtest.js";
3
+ import type { Authentication } from "../authentication.service.js";
4
+ import type { EmailPasswordProvider } from "../email/emailpassword.service.js";
5
+ /**
6
+ * Base class for email/password provider specs, using the full test/config.json
7
+ */
8
+ export declare abstract class EmailTest extends AuthTest {
9
+ auth: Authentication;
10
+ email: EmailPasswordProvider;
11
+ /** @override */
12
+ tweakApp(app: TestApplication): Promise<void>;
13
+ /** @override */
14
+ beforeEach(): Promise<void>;
15
+ /**
16
+ * @returns the url of the last sent email
17
+ */
18
+ lastMailUrl(): string;
19
+ /**
20
+ * @param url - an emailed link
21
+ * @returns its token query parameter
22
+ */
23
+ tokenOf(url: string): string;
24
+ }
25
+ //# sourceMappingURL=emailtest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emailtest.d.ts","sourceRoot":"","sources":["../../src/test/emailtest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAE/E;;GAEG;AACH,8BAAsB,SAAU,SAAQ,QAAQ;IAC9C,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,qBAAqB,CAAC;IAE7B,gBAAgB;IACV,QAAQ,CAAC,GAAG,EAAE,eAAe,iBAIlC;IAED,gBAAgB;IACV,UAAU,kBASf;IAED;;OAEG;IACH,WAAW,IAAI,MAAM,CAEpB;IAED;;;OAGG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3B;CACF"}
@@ -0,0 +1,39 @@
1
+ import { useService } from "@webda/core";
2
+ import { AuthTest } from "./authtest.js";
3
+ import { addStubProviders } from "./stubprovider.js";
4
+ /**
5
+ * Base class for email/password provider specs, using the full test/config.json
6
+ */
7
+ export class EmailTest extends AuthTest {
8
+ /** @override */
9
+ async tweakApp(app) {
10
+ await super.tweakApp(app);
11
+ // test/config.json declares a "google" provider of this type
12
+ addStubProviders(app, "google");
13
+ }
14
+ /** @override */
15
+ async beforeEach() {
16
+ await super.beforeEach();
17
+ this.auth = useService("Authentication");
18
+ this.email = useService("emailAuth");
19
+ const params = this.email.getParameters();
20
+ params.verification = "after";
21
+ params.throttle = { resendDelay: 14400000, failedBeforeDelay: 3, lockout: 900000 };
22
+ params.password = { policy: ".{8,}" };
23
+ this.email.resolve();
24
+ }
25
+ /**
26
+ * @returns the url of the last sent email
27
+ */
28
+ lastMailUrl() {
29
+ return this.mailer.sent[this.mailer.sent.length - 1].replacements.url;
30
+ }
31
+ /**
32
+ * @param url - an emailed link
33
+ * @returns its token query parameter
34
+ */
35
+ tokenOf(url) {
36
+ return new URL(url, "http://localhost").searchParams.get("token");
37
+ }
38
+ }
39
+ //# sourceMappingURL=emailtest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emailtest.js","sourceRoot":"","sources":["../../src/test/emailtest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAIrD;;GAEG;AACH,MAAM,OAAgB,SAAU,SAAQ,QAAQ;IAI9C,gBAAgB;IAChB,KAAK,CAAC,QAAQ,CAAC,GAAoB;QACjC,MAAM,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC1B,6DAA6D;QAC7D,gBAAgB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;IAED,gBAAgB;IAChB,KAAK,CAAC,UAAU;QACd,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,gBAAuB,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,WAAkB,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;QAC1C,MAAM,CAAC,YAAY,GAAG,OAAO,CAAC;QAC9B,MAAM,CAAC,QAAQ,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QACnF,MAAM,CAAC,QAAQ,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,WAAW;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC;IACxE,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,GAAW;QACjB,OAAO,IAAI,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpE,CAAC;CACF"}
@@ -0,0 +1,45 @@
1
+ import type { ResolvedIdentity } from "@webda/core";
2
+ import { type OAuthAuthorizationRequest, type OAuthCallbackRequest, OAuthProvider, OAuthProviderParameters, type OAuthTokenRequest } from "../oauth/oauth.service.js";
3
+ /**
4
+ * OAuth provider used by the specs: no network, codes and tokens describe the identity
5
+ *
6
+ * A code (or token) is `<sub>,<email>,<verified 0|1>[,<provider override>]`; `bad` throws TokenInvalid, a value
7
+ * starting with `boom` a plain Error whose message contains the value. Every exchange is recorded in `calls`.
8
+ */
9
+ export declare class FakeOAuthProvider extends OAuthProvider {
10
+ readonly providerName = "fake";
11
+ /**
12
+ * Registered with addModda (no module metadata): build the parameters like a compiled modda would
13
+ * @param params - raw parameters
14
+ * @returns the loaded parameters
15
+ */
16
+ static createConfiguration(params: any): OAuthProviderParameters;
17
+ /** Recorded exchanges */
18
+ calls: {
19
+ method: string;
20
+ value: string;
21
+ request?: any;
22
+ }[];
23
+ /**
24
+ * @param request - authorization request
25
+ * @returns the fake authorization url
26
+ */
27
+ getAuthorizationUrl(request: OAuthAuthorizationRequest): string;
28
+ /**
29
+ * @param value - code or token
30
+ * @param tokens - tokens to attach
31
+ * @returns the identity it describes
32
+ */
33
+ protected identityOf(value: string, tokens?: any): ResolvedIdentity;
34
+ /**
35
+ * @param request - callback request
36
+ * @returns identity
37
+ */
38
+ handleCallback(request: OAuthCallbackRequest): Promise<ResolvedIdentity>;
39
+ /**
40
+ * @param request - token request
41
+ * @returns identity
42
+ */
43
+ handleToken(request: OAuthTokenRequest): Promise<ResolvedIdentity>;
44
+ }
45
+ //# sourceMappingURL=fakeoauth.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeoauth.service.d.ts","sourceRoot":"","sources":["../../src/test/fakeoauth.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,aAAa,EACb,uBAAuB,EACvB,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAC;AAEnC;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,aAAa;IAClD,QAAQ,CAAC,YAAY,UAAU;IAE/B;;;;OAIG;IACH,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE,GAAG,GAAG,uBAAuB,CAE/D;IAED,yBAAyB;IACzB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,EAAE,CAAM;IAE/D;;;OAGG;IACH,mBAAmB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,CAU9D;IAED;;;;OAIG;IACH,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,GAAG,gBAAgB,CAalE;IAED;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAG7E;IAED;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAIvE;CACF"}
@@ -0,0 +1,78 @@
1
+ import { TokenInvalid } from "../errors.js";
2
+ import { OAuthProvider, OAuthProviderParameters } from "../oauth/oauth.service.js";
3
+ /**
4
+ * OAuth provider used by the specs: no network, codes and tokens describe the identity
5
+ *
6
+ * A code (or token) is `<sub>,<email>,<verified 0|1>[,<provider override>]`; `bad` throws TokenInvalid, a value
7
+ * starting with `boom` a plain Error whose message contains the value. Every exchange is recorded in `calls`.
8
+ */
9
+ export class FakeOAuthProvider extends OAuthProvider {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.providerName = "fake";
13
+ /** Recorded exchanges */
14
+ this.calls = [];
15
+ }
16
+ /**
17
+ * Registered with addModda (no module metadata): build the parameters like a compiled modda would
18
+ * @param params - raw parameters
19
+ * @returns the loaded parameters
20
+ */
21
+ static createConfiguration(params) {
22
+ return new OAuthProviderParameters().load(params);
23
+ }
24
+ /**
25
+ * @param request - authorization request
26
+ * @returns the fake authorization url
27
+ */
28
+ getAuthorizationUrl(request) {
29
+ const url = new URL("https://idp.example/authorize");
30
+ url.searchParams.set("state", request.state);
31
+ url.searchParams.set("redirect_uri", request.redirectUri);
32
+ url.searchParams.set("scope", request.scope.join(" "));
33
+ url.searchParams.set("client_id", this.parameters.client_id);
34
+ url.searchParams.set("code_challenge", request.codeChallenge);
35
+ url.searchParams.set("code_challenge_method", request.codeChallengeMethod);
36
+ url.searchParams.set("nonce", request.nonce);
37
+ return url.toString();
38
+ }
39
+ /**
40
+ * @param value - code or token
41
+ * @param tokens - tokens to attach
42
+ * @returns the identity it describes
43
+ */
44
+ identityOf(value, tokens) {
45
+ if (value === "bad")
46
+ throw new TokenInvalid();
47
+ if (value?.startsWith("boom"))
48
+ throw new Error(`secret internal failure: ${value}`);
49
+ const [sub, email, verified, provider] = value.split(",");
50
+ return {
51
+ provider: provider ?? this.providerName,
52
+ providerUid: sub,
53
+ email: email || undefined,
54
+ emailVerified: verified === "1",
55
+ profile: { name: `Fake ${sub}` },
56
+ tokens: tokens ?? { access_token: `at-${sub}` },
57
+ amr: ["oauth"]
58
+ };
59
+ }
60
+ /**
61
+ * @param request - callback request
62
+ * @returns identity
63
+ */
64
+ async handleCallback(request) {
65
+ this.calls.push({ method: "callback", value: request.code, request });
66
+ return this.identityOf(request.code);
67
+ }
68
+ /**
69
+ * @param request - token request
70
+ * @returns identity
71
+ */
72
+ async handleToken(request) {
73
+ const value = request.token ?? request.tokens?.id_token;
74
+ this.calls.push({ method: "token", value, request });
75
+ return this.identityOf(value, request.tokens);
76
+ }
77
+ }
78
+ //# sourceMappingURL=fakeoauth.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeoauth.service.js","sourceRoot":"","sources":["../../src/test/fakeoauth.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { Service } from "@webda/core";
2
+ /**
3
+ * @param name - provider name
4
+ * @returns a minimal AuthProvider service class named `name` (specs call complete() on its behalf)
5
+ */
6
+ export function stubProvider(name) {
7
+ return class StubProvider extends Service {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.providerName = name;
11
+ }
12
+ /**
13
+ * @returns public info
14
+ */
15
+ getPublicInfo() {
16
+ return { name, type: "oauth", startUrl: `/auth/${name}` };
17
+ }
18
+ };
19
+ }
20
+ /**
21
+ * Register stub providers in a test application: add `<name>: { type: "WebdaTest/Stub_<name>" }` to the services
22
+ * @param app - test application
23
+ * @param names - provider names
24
+ */
25
+ export function addStubProviders(app, ...names) {
26
+ for (const name of names) {
27
+ app.addModda(`WebdaTest/Stub_${name}`, stubProvider(name));
28
+ }
29
+ }
30
+ /**
31
+ * @param names - provider names
32
+ * @returns the services configuration of the stub providers
33
+ */
34
+ export function stubProvidersConfig(...names) {
35
+ return Object.fromEntries(names.map(name => [`${name}Stub`, { type: `WebdaTest/Stub_${name}` }]));
36
+ }
37
+ //# sourceMappingURL=stubprovider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stubprovider.js","sourceRoot":"","sources":["../../src/test/stubprovider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,MAAM,YAAa,SAAQ,OAAO;QAAlC;;YACI,iBAAY,GAAG,IAAI,CAAC;QAQ/B,CAAC;QANC;;WAEG;QACH,aAAa;YACX,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAgB,EAAE,QAAQ,EAAE,SAAS,IAAI,EAAE,EAAE,CAAC;QACrE,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAoB,EAAE,GAAG,KAAe;IACvE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,GAAG,CAAC,QAAQ,CAAC,kBAAkB,IAAI,EAAE,EAAE,YAAY,CAAC,IAAI,CAAQ,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAG,KAAe;IACpD,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,MAAM,EAAE,EAAE,IAAI,EAAE,kBAAkB,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AACpG,CAAC"}
@@ -0,0 +1,29 @@
1
+ /** Writes a raw row into a store's backing storage */
2
+ export type RawWriter = (key: string, row: any) => void;
3
+ /**
4
+ * Seed a v3 email user and its ident RAW (Ruling R1): the v3 layout is written straight into the storage,
5
+ * bypassing model constructors and serializers
6
+ * @param write - raw storage writer (MemoryStore storage map, FileStore folder, ...)
7
+ * @param email - email (normalised)
8
+ * @param opts - options
9
+ * @param opts.validated - whether the v3 ident was validated
10
+ * @param opts.password - clear password hashed into `__password`
11
+ * @param opts.userId - user uuid
12
+ * @returns the user uuid
13
+ */
14
+ export declare function seedV3(write: RawWriter, email: string, opts: {
15
+ validated: boolean;
16
+ password: string;
17
+ userId?: string;
18
+ }): Promise<string>;
19
+ /**
20
+ * Seed a raw v3 ident `"<uid>_<provider>"`
21
+ * @param write - raw storage writer
22
+ * @param uid - provider uid
23
+ * @param provider - provider
24
+ * @param userId - owner uuid
25
+ * @param extra - extra v3 fields
26
+ * @returns the v3 key
27
+ */
28
+ export declare function seedV3Ident(write: RawWriter, uid: string, provider: string, userId: string, extra?: any): string;
29
+ //# sourceMappingURL=v3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3.d.ts","sourceRoot":"","sources":["../../src/test/v3.ts"],"names":[],"mappings":"AAEA,sDAAsD;AACtD,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;AAExD;;;;;;;;;;GAUG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9D,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,GAAQ,GAAG,MAAM,CAIpH"}
package/lib/test/v3.js ADDED
@@ -0,0 +1,47 @@
1
+ import { Password } from "@webda/core";
2
+ /**
3
+ * Seed a v3 email user and its ident RAW (Ruling R1): the v3 layout is written straight into the storage,
4
+ * bypassing model constructors and serializers
5
+ * @param write - raw storage writer (MemoryStore storage map, FileStore folder, ...)
6
+ * @param email - email (normalised)
7
+ * @param opts - options
8
+ * @param opts.validated - whether the v3 ident was validated
9
+ * @param opts.password - clear password hashed into `__password`
10
+ * @param opts.userId - user uuid
11
+ * @returns the user uuid
12
+ */
13
+ export async function seedV3(write, email, opts) {
14
+ const hash = new Password();
15
+ await hash.set(opts.password);
16
+ const userId = opts.userId ?? `v3-${email}`;
17
+ write(userId, {
18
+ uuid: userId,
19
+ __type: "Webda/User",
20
+ email,
21
+ displayName: email,
22
+ __password: hash.__hash,
23
+ _lastPasswordRecovery: 0
24
+ });
25
+ seedV3Ident(write, email, "email", userId, {
26
+ email,
27
+ _failedLogin: 1,
28
+ _lastValidationEmail: 42,
29
+ ...(opts.validated ? { _validation: "2020-01-01T00:00:00.000Z" } : {})
30
+ });
31
+ return userId;
32
+ }
33
+ /**
34
+ * Seed a raw v3 ident `"<uid>_<provider>"`
35
+ * @param write - raw storage writer
36
+ * @param uid - provider uid
37
+ * @param provider - provider
38
+ * @param userId - owner uuid
39
+ * @param extra - extra v3 fields
40
+ * @returns the v3 key
41
+ */
42
+ export function seedV3Ident(write, uid, provider, userId, extra = {}) {
43
+ const key = `${uid}_${provider}`;
44
+ write(key, { uuid: key, __type: "Webda/Ident", _type: provider, _user: userId, ...extra });
45
+ return key;
46
+ }
47
+ //# sourceMappingURL=v3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3.js","sourceRoot":"","sources":["../../src/test/v3.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAKvC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,KAAgB,EAChB,KAAa,EACb,IAA+D;IAE/D,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC5B,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,EAAE,CAAC;IAC5C,KAAK,CAAC,MAAM,EAAE;QACZ,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,YAAY;QACpB,KAAK;QACL,WAAW,EAAE,KAAK;QAClB,UAAU,EAAE,IAAI,CAAC,MAAM;QACvB,qBAAqB,EAAE,CAAC;KACzB,CAAC,CAAC;IACH,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE;QACzC,KAAK;QACL,YAAY,EAAE,CAAC;QACf,oBAAoB,EAAE,EAAE;QACxB,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,0BAA0B,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvE,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,KAAgB,EAAE,GAAW,EAAE,QAAgB,EAAE,MAAc,EAAE,KAAK,GAAQ,EAAE;IAC1G,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,QAAQ,EAAE,CAAC;IACjC,KAAK,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;IAC3F,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { Ident, IdentThrottle } from "@webda/core";
2
+ /** Login failure state of an ident: its top-level `_loginAttempts` / `_lastLoginAttemptAt` attributes */
3
+ export type LoginAttempts = Pick<Ident, "_loginAttempts" | "_lastLoginAttemptAt">;
4
+ /**
5
+ * Check if an ident is locked due to too many failed logins
6
+ * @param s - login failure state (an ident)
7
+ * @param failedBefore - failures before locking
8
+ * @param lockoutMs - lock duration after the last attempt
9
+ * @param now - clock
10
+ * @returns true while locked
11
+ */
12
+ export declare function isLocked(s: LoginAttempts, failedBefore: number, lockoutMs: number, now?: number): boolean;
13
+ /**
14
+ * Record a failure in the login failure state
15
+ * @param s - login failure state
16
+ * @param now - clock
17
+ * @returns state with one more failure
18
+ */
19
+ export declare function recordFailure(s: LoginAttempts, now?: number): LoginAttempts;
20
+ /**
21
+ * Clear all login failures
22
+ * @returns state with failures cleared
23
+ */
24
+ export declare function resetFailures(): LoginAttempts;
25
+ /**
26
+ * Check if a message can be sent based on throttle delay
27
+ * @param t - throttle state
28
+ * @param delayMs - minimal delay between two emails
29
+ * @param now - clock
30
+ * @returns true when an email may be sent
31
+ */
32
+ export declare function canSend(t: IdentThrottle, delayMs: number, now?: number): boolean;
33
+ /**
34
+ * Mark that a message was sent
35
+ * @param t - throttle state
36
+ * @param now - clock
37
+ * @returns state with the send timestamp set
38
+ */
39
+ export declare function markSent(t: IdentThrottle, now?: number): IdentThrottle;
40
+ //# sourceMappingURL=throttle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAExD,yGAAyG;AACzG,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,EAAE,gBAAgB,GAAG,qBAAqB,CAAC,CAAC;AAElF;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAErH;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,GAAE,MAAmB,GAAG,aAAa,CAEvF;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,aAAa,CAE7C;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAE5F;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,GAAE,MAAmB,GAAG,aAAa,CAElF"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Check if an ident is locked due to too many failed logins
3
+ * @param s - login failure state (an ident)
4
+ * @param failedBefore - failures before locking
5
+ * @param lockoutMs - lock duration after the last attempt
6
+ * @param now - clock
7
+ * @returns true while locked
8
+ */
9
+ export function isLocked(s, failedBefore, lockoutMs, now = Date.now()) {
10
+ return !!s && (s._loginAttempts ?? 0) >= failedBefore && (s._lastLoginAttemptAt ?? 0) + lockoutMs > now;
11
+ }
12
+ /**
13
+ * Record a failure in the login failure state
14
+ * @param s - login failure state
15
+ * @param now - clock
16
+ * @returns state with one more failure
17
+ */
18
+ export function recordFailure(s, now = Date.now()) {
19
+ return { _loginAttempts: (s?._loginAttempts ?? 0) + 1, _lastLoginAttemptAt: now };
20
+ }
21
+ /**
22
+ * Clear all login failures
23
+ * @returns state with failures cleared
24
+ */
25
+ export function resetFailures() {
26
+ return { _loginAttempts: 0, _lastLoginAttemptAt: undefined };
27
+ }
28
+ /**
29
+ * Check if a message can be sent based on throttle delay
30
+ * @param t - throttle state
31
+ * @param delayMs - minimal delay between two emails
32
+ * @param now - clock
33
+ * @returns true when an email may be sent
34
+ */
35
+ export function canSend(t, delayMs, now = Date.now()) {
36
+ return !t?.lastSentAt || t.lastSentAt + delayMs < now;
37
+ }
38
+ /**
39
+ * Mark that a message was sent
40
+ * @param t - throttle state
41
+ * @param now - clock
42
+ * @returns state with the send timestamp set
43
+ */
44
+ export function markSent(t, now = Date.now()) {
45
+ return { ...(t ?? {}), lastSentAt: now };
46
+ }
47
+ //# sourceMappingURL=throttle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.js","sourceRoot":"","sources":["../src/throttle.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,CAAgB,EAAE,YAAoB,EAAE,SAAiB,EAAE,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;IAC1G,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,IAAI,YAAY,IAAI,CAAC,CAAC,CAAC,mBAAmB,IAAI,CAAC,CAAC,GAAG,SAAS,GAAG,GAAG,CAAC;AAC1G,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,CAAgB,EAAE,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;IACtE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC,EAAE,cAAc,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC;AACpF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,EAAE,cAAc,EAAE,CAAC,EAAE,mBAAmB,EAAE,SAAS,EAAE,CAAC;AAC/D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,CAAgB,EAAE,OAAe,EAAE,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;IACjF,OAAO,CAAC,CAAC,EAAE,UAAU,IAAI,CAAC,CAAC,UAAU,GAAG,OAAO,GAAG,GAAG,CAAC;AACxD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,CAAgB,EAAE,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;IACjE,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;AAC3C,CAAC"}