@micacms/auth-oidc 0.2.2 → 0.4.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.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > **Experimental.** Pre-1.0: the API can still change without notice.
4
4
 
5
5
  The server-only OIDC gate for Mica: the authorization code flow with PKCE,
6
- sessions in SQLite, admission rules over provider claims, and the middleware
6
+ sessions in an encrypted cookie, admission rules over provider claims, and the middleware
7
7
  gate the runtime mounts in front of the admin API.
8
8
 
9
9
  It is a dependency of `@micacms/self-hosted` and of nothing else. In
@@ -11,13 +11,14 @@
11
11
  *
12
12
  * MICA_ADMIT_GROUP=mica-editors a claim the provider sends
13
13
  * MICA_ADMIT_DOMAIN=example.com every verified address there
14
+ * MICA_ADMIT_EMAILS=alice@gmail.com these verified addresses
14
15
  * MICA_ADMIT_SUBJECTS=https://id.example.com|a1b2c3
15
16
  *
16
17
  * The group rule is the one to grow into. Adding an editor is then a group
17
18
  * change in the identity provider — where the operator already manages
18
19
  * joiners and leavers — and Mica is not touched, not restarted, and holds no
19
- * copy of the list to go stale. The subject list exists for a provider with no
20
- * groups, and for the first sign-in before a group exists.
20
+ * copy of the list to go stale. The email list exists for a provider with no
21
+ * groups, such as Google; the subject list for one that verifies no addresses.
21
22
  *
22
23
  * What this costs, and it is a real cost: removing somebody from a group at
23
24
  * the provider does not take effect until their next sign-in, because the
@@ -27,7 +28,7 @@
27
28
  * decision in docs/decisions.md.
28
29
  */
29
30
  import type { AuthConfig } from "./config.js";
30
- import type { SessionIdentity } from "./sessions-repository.js";
31
+ import type { SessionIdentity } from "./session.js";
31
32
  export type AdmissionResult = {
32
33
  admitted: true;
33
34
  } | {
@@ -39,5 +40,5 @@ export interface AdmissionPolicy {
39
40
  readonly summary: string;
40
41
  admits(identity: SessionIdentity): AdmissionResult;
41
42
  }
42
- export declare function createAdmissionPolicy(config: Pick<AuthConfig, "admitGroups" | "admitDomains" | "admitSubjects">): AdmissionPolicy;
43
+ export declare function createAdmissionPolicy(config: Pick<AuthConfig, "admitGroups" | "admitDomains" | "admitEmails" | "admitSubjects">): AdmissionPolicy;
43
44
  //# sourceMappingURL=admission.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admission.d.ts","sourceRoot":"","sources":["../src/admission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,MAAM,eAAe,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvF,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CAAC;CACpD;AAsCD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,cAAc,GAAG,eAAe,CAAC,GACzE,eAAe,CA+CjB"}
1
+ {"version":3,"file":"admission.d.ts","sourceRoot":"","sources":["../src/admission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,MAAM,eAAe,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvF,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CAAC;CACpD;AAsCD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,cAAc,GAAG,aAAa,GAAG,eAAe,CAAC,GACzF,eAAe,CAsDjB"}
package/dist/admission.js CHANGED
@@ -11,13 +11,14 @@
11
11
  *
12
12
  * MICA_ADMIT_GROUP=mica-editors a claim the provider sends
13
13
  * MICA_ADMIT_DOMAIN=example.com every verified address there
14
+ * MICA_ADMIT_EMAILS=alice@gmail.com these verified addresses
14
15
  * MICA_ADMIT_SUBJECTS=https://id.example.com|a1b2c3
15
16
  *
16
17
  * The group rule is the one to grow into. Adding an editor is then a group
17
18
  * change in the identity provider — where the operator already manages
18
19
  * joiners and leavers — and Mica is not touched, not restarted, and holds no
19
- * copy of the list to go stale. The subject list exists for a provider with no
20
- * groups, and for the first sign-in before a group exists.
20
+ * copy of the list to go stale. The email list exists for a provider with no
21
+ * groups, such as Google; the subject list for one that verifies no addresses.
21
22
  *
22
23
  * What this costs, and it is a real cost: removing somebody from a group at
23
24
  * the provider does not take effect until their next sign-in, because the
@@ -27,17 +28,16 @@
27
28
  * decision in docs/decisions.md.
28
29
  */
29
30
  /**
30
- * The subject is the account key, and an email address is never one.
31
- *
32
- * Most providers let a person change their address, and some let two people
33
- * hold one in turn. Keying admission on an address is therefore a standing
34
- * offer to whoever holds it next, which is a well-trodden route to account
35
- * takeover. The issuer is part of the key because a subject is only unique
36
- * within the provider that issued it.
31
+ * The strictest rule: the subject never changes and is never reassigned. The
32
+ * issuer is part of the key because a subject is only unique within the
33
+ * provider that issued it.
37
34
  */
38
35
  function matchesSubject(identity, entries) {
39
36
  return entries.some((entry) => entry.issuer === identity.issuer && entry.subject === identity.subject);
40
37
  }
38
+ function matchesEmail(identity, emails) {
39
+ return emails.includes(identity.email.toLowerCase());
40
+ }
41
41
  function matchesDomain(identity, domains) {
42
42
  const at = identity.email.lastIndexOf("@");
43
43
  if (at < 0)
@@ -56,10 +56,11 @@ function matchesGroup(identity, groups) {
56
56
  return groups.some((group) => held.has(group.replace(/^\//, "")));
57
57
  }
58
58
  export function createAdmissionPolicy(config) {
59
- const { admitGroups, admitDomains, admitSubjects } = config;
59
+ const { admitGroups, admitDomains, admitEmails, admitSubjects } = config;
60
60
  const summary = [
61
61
  admitGroups.length > 0 ? `group=${admitGroups.join("|")}` : null,
62
62
  admitDomains.length > 0 ? `domain=${admitDomains.join("|")}` : null,
63
+ admitEmails.length > 0 ? `emails=${admitEmails.length}` : null,
63
64
  admitSubjects.length > 0 ? `subjects=${admitSubjects.length}` : null,
64
65
  ]
65
66
  .filter(Boolean)
@@ -73,22 +74,28 @@ export function createAdmissionPolicy(config) {
73
74
  return { admitted: true };
74
75
  if (matchesGroup(identity, admitGroups))
75
76
  return { admitted: true };
76
- if (admitDomains.length > 0 && matchesDomain(identity, admitDomains)) {
77
+ const byEmail = matchesEmail(identity, admitEmails);
78
+ if (byEmail || matchesDomain(identity, admitDomains)) {
77
79
  /**
78
- * A domain rule trusts the provider to have checked the address.
80
+ * An email or domain rule trusts the provider to have checked the address.
79
81
  *
80
82
  * Where a provider allows self-registration, an unverified address is
81
83
  * a claim the person typed. Admitting on that would let anybody enter
82
- * a domain they do not hold, so the rule refuses and says why — the
84
+ * an address they do not hold, so the rule refuses and says why — the
83
85
  * operator can turn verification on, or use a group or subject rule
84
86
  * instead.
87
+ *
88
+ * What remains is reassignment: an organisation that gives a former
89
+ * editor's address to somebody new gives them this site too. Public
90
+ * providers such as Gmail never reuse an address; the trade is
91
+ * recorded in docs/decisions.md.
85
92
  */
86
93
  if (identity.emailVerified)
87
94
  return { admitted: true };
88
95
  return {
89
96
  admitted: false,
90
- reason: `${identity.email} is in an admitted domain, but the identity provider ` +
91
- "does not report the address as verified.",
97
+ reason: `${identity.email} is ${byEmail ? "an admitted address" : "in an admitted domain"}, ` +
98
+ "but the identity provider does not report the address as verified.",
92
99
  };
93
100
  }
94
101
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"admission.js","sourceRoot":"","sources":["../src/admission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAaH;;;;;;;;GAQG;AACH,SAAS,cAAc,CACrB,QAAyB,EACzB,OAA2D;IAE3D,OAAO,OAAO,CAAC,IAAI,CACjB,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,OAAO,CAClF,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,QAAyB,EAAE,OAA0B;IAC1E,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,EAAE,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACzB,OAAO,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,QAAyB,EAAE,MAAyB;IACxE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/E,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,MAA0E;IAE1E,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC;IAE5D,MAAM,OAAO,GAAG;QACd,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;QAChE,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;QACnE,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI;KACrE;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;QACL,OAAO;QAEP,MAAM,CAAC,QAAQ;YACb,sEAAsE;YACtE,gEAAgE;YAChE,IAAI,cAAc,CAAC,QAAQ,EAAE,aAAa,CAAC;gBAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YAEvE,IAAI,YAAY,CAAC,QAAQ,EAAE,WAAW,CAAC;gBAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YAEnE,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;gBACrE;;;;;;;;mBAQG;gBACH,IAAI,QAAQ,CAAC,aAAa;oBAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;gBAEtD,OAAO;oBACL,QAAQ,EAAE,KAAK;oBACf,MAAM,EACJ,GAAG,QAAQ,CAAC,KAAK,uDAAuD;wBACxE,0CAA0C;iBAC7C,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,OAAO,gCAAgC;aAC9E,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"admission.js","sourceRoot":"","sources":["../src/admission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAaH;;;;GAIG;AACH,SAAS,cAAc,CACrB,QAAyB,EACzB,OAA2D;IAE3D,OAAO,OAAO,CAAC,IAAI,CACjB,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,OAAO,CAClF,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,QAAyB,EAAE,MAAyB;IACxE,OAAO,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,aAAa,CAAC,QAAyB,EAAE,OAA0B;IAC1E,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,EAAE,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACzB,OAAO,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,QAAyB,EAAE,MAAyB;IACxE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/E,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,MAA0F;IAE1F,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC;IAEzE,MAAM,OAAO,GAAG;QACd,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;QAChE,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;QACnE,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI;QAC9D,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI;KACrE;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;QACL,OAAO;QAEP,MAAM,CAAC,QAAQ;YACb,sEAAsE;YACtE,gEAAgE;YAChE,IAAI,cAAc,CAAC,QAAQ,EAAE,aAAa,CAAC;gBAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YAEvE,IAAI,YAAY,CAAC,QAAQ,EAAE,WAAW,CAAC;gBAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YAEnE,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YACpD,IAAI,OAAO,IAAI,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;gBACrD;;;;;;;;;;;;;mBAaG;gBACH,IAAI,QAAQ,CAAC,aAAa;oBAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;gBAEtD,OAAO;oBACL,QAAQ,EAAE,KAAK;oBACf,MAAM,EACJ,GAAG,QAAQ,CAAC,KAAK,OAAO,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,uBAAuB,IAAI;wBACrF,oEAAoE;iBACvE,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,OAAO,gCAAgC;aAC9E,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
package/dist/config.d.ts CHANGED
@@ -28,11 +28,13 @@ export declare const authEnv: {
28
28
  /**
29
29
  * Who may edit this site. Authentication says who somebody is; these say
30
30
  * whether that person gets in. At least one is required — the composition
31
- * refuses to start without one. Comma-separated, all three.
31
+ * refuses to start without one. Comma-separated, all four.
32
32
  */
33
33
  MICA_ADMIT_GROUP: z.ZodOptional<z.ZodString>;
34
34
  MICA_ADMIT_DOMAIN: z.ZodOptional<z.ZodString>;
35
- /** `issuer|subject` pairs. Never an email — see admission.ts. */
35
+ /** Verified addresses — the list for a provider with no groups, such as Google. */
36
+ MICA_ADMIT_EMAILS: z.ZodOptional<z.ZodString>;
37
+ /** `issuer|subject` pairs — see admission.ts. */
36
38
  MICA_ADMIT_SUBJECTS: z.ZodOptional<z.ZodString>;
37
39
  /** Signs session cookies. Rotating it invalidates every session. */
38
40
  SESSION_SECRET: z.ZodOptional<z.ZodString>;
@@ -55,6 +57,7 @@ declare const authSchema: z.ZodObject<{
55
57
  MICA_OIDC_GROUPS_CLAIM: z.ZodDefault<z.ZodString>;
56
58
  MICA_ADMIT_GROUP: z.ZodOptional<z.ZodString>;
57
59
  MICA_ADMIT_DOMAIN: z.ZodOptional<z.ZodString>;
60
+ MICA_ADMIT_EMAILS: z.ZodOptional<z.ZodString>;
58
61
  MICA_ADMIT_SUBJECTS: z.ZodOptional<z.ZodString>;
59
62
  SESSION_SECRET: z.ZodOptional<z.ZodString>;
60
63
  SESSION_TTL_HOURS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -75,6 +78,7 @@ export declare function deriveAuth<T extends AuthEnv & {
75
78
  oidcAllowInsecure: boolean;
76
79
  admitGroups: string[];
77
80
  admitDomains: string[];
81
+ admitEmails: string[];
78
82
  admitSubjects: {
79
83
  issuer: string;
80
84
  subject: string;
@@ -93,6 +97,7 @@ export interface AuthConfig {
93
97
  oidcAllowInsecure: boolean;
94
98
  admitGroups: string[];
95
99
  admitDomains: string[];
100
+ admitEmails: string[];
96
101
  admitSubjects: Array<{
97
102
  issuer: string;
98
103
  subject: string;
@@ -117,6 +122,7 @@ export declare function requireAuthConfig(config: {
117
122
  MICA_OIDC_CLIENT_ID?: string | undefined;
118
123
  admitGroups: string[];
119
124
  admitDomains: string[];
125
+ admitEmails: string[];
120
126
  admitSubjects: Array<{
121
127
  issuer: string;
122
128
  subject: string;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,OAAO;IAClB;;;;OAIG;;;IAGH,iDAAiD;;;IAGjD;;;;;OAKG;;IAGH;;;;OAIG;;;IAGH,iEAAiE;;IAGjE,oEAAoE;;IAEpE;;;;;OAKG;;;;;CAUJ,CAAC;AAEF,QAAA,MAAM,UAAU;;;;;;;;;;;;;;iBAAoB,CAAC;AACrC,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEjD,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,EAC1F,GAAG,EAAE,CAAC;IAIJ;;;OAGG;;;;;;;;;EAqBN;AAED,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,uBAAuB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3D,GAAG,IAAI,CA2CP"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,OAAO;IAClB;;;;OAIG;;;IAGH,iDAAiD;;;IAGjD;;;;;OAKG;;IAGH;;;;OAIG;;;IAGH,mFAAmF;;IAEnF,iDAAiD;;IAGjD,oEAAoE;;IAEpE;;;;;OAKG;;;;;CAUJ,CAAC;AAEF,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;iBAAoB,CAAC;AACrC,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEjD,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,EAC1F,GAAG,EAAE,CAAC;IAIJ;;;OAGG;;;;;;;;;;EAsBN;AAED,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,uBAAuB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;CACzB;AA0BD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3D,GAAG,IAAI,CA4DP"}
package/dist/config.js CHANGED
@@ -29,11 +29,13 @@ export const authEnv = {
29
29
  /**
30
30
  * Who may edit this site. Authentication says who somebody is; these say
31
31
  * whether that person gets in. At least one is required — the composition
32
- * refuses to start without one. Comma-separated, all three.
32
+ * refuses to start without one. Comma-separated, all four.
33
33
  */
34
34
  MICA_ADMIT_GROUP: z.string().optional(),
35
35
  MICA_ADMIT_DOMAIN: z.string().optional(),
36
- /** `issuer|subject` pairs. Never an email — see admission.ts. */
36
+ /** Verified addresses — the list for a provider with no groups, such as Google. */
37
+ MICA_ADMIT_EMAILS: z.string().optional(),
38
+ /** `issuer|subject` pairs — see admission.ts. */
37
39
  MICA_ADMIT_SUBJECTS: z.string().optional(),
38
40
  /** Signs session cookies. Rotating it invalidates every session. */
39
41
  SESSION_SECRET: z.string().min(32).optional(),
@@ -67,6 +69,7 @@ export function deriveAuth(env) {
67
69
  // Lowercased: a domain is case-insensitive, and an operator who writes
68
70
  // `Example.com` means the same thing the provider spells `example.com`.
69
71
  admitDomains: list(env.MICA_ADMIT_DOMAIN).map((domain) => domain.toLowerCase().replace(/^@/, "")),
72
+ admitEmails: list(env.MICA_ADMIT_EMAILS).map((email) => email.toLowerCase()),
70
73
  admitSubjects: list(env.MICA_ADMIT_SUBJECTS).map((entry) => {
71
74
  const separator = entry.indexOf("|");
72
75
  return {
@@ -79,6 +82,29 @@ export function deriveAuth(env) {
79
82
  cookieSecure: env.MICA_PUBLIC_URL.startsWith("https://"),
80
83
  };
81
84
  }
85
+ /**
86
+ * Domains where anybody can register an address. A domain rule naming one
87
+ * admits the public, so it is refused; name the addresses instead.
88
+ */
89
+ const PUBLIC_EMAIL_DOMAINS = new Set([
90
+ "gmail.com",
91
+ "googlemail.com",
92
+ "outlook.com",
93
+ "hotmail.com",
94
+ "live.com",
95
+ "msn.com",
96
+ "yahoo.com",
97
+ "icloud.com",
98
+ "me.com",
99
+ "aol.com",
100
+ "proton.me",
101
+ "protonmail.com",
102
+ "gmx.com",
103
+ "gmx.net",
104
+ "gmx.de",
105
+ "gmx.ch",
106
+ "web.de",
107
+ ]);
82
108
  /**
83
109
  * The refusals that make a deployment honest before it serves a request.
84
110
  *
@@ -96,23 +122,34 @@ export function requireAuthConfig(config) {
96
122
  }
97
123
  if (config.admitGroups.length === 0 &&
98
124
  config.admitDomains.length === 0 &&
125
+ config.admitEmails.length === 0 &&
99
126
  config.admitSubjects.length === 0) {
100
127
  throw new ConfigError("Nothing says who may edit this site.\n" +
101
128
  " Authentication is not admission: your provider will happily vouch for\n" +
102
129
  " everybody it knows. Set at least one of these:\n\n" +
103
130
  " MICA_ADMIT_GROUP=mica-editors a claim your provider sends\n" +
104
131
  " MICA_ADMIT_DOMAIN=example.com every verified address there\n" +
132
+ " MICA_ADMIT_EMAILS=alice@gmail.com these verified addresses\n" +
105
133
  " MICA_ADMIT_SUBJECTS=https://id.example.com|a1b2c3\n\n" +
106
134
  " The group rule is the one to grow into: adding an editor is then a\n" +
107
135
  " change in the identity provider, and Mica is not touched.");
108
136
  }
137
+ const open = config.admitDomains.filter((domain) => PUBLIC_EMAIL_DOMAINS.has(domain));
138
+ if (open.length > 0) {
139
+ throw new ConfigError(`MICA_ADMIT_DOMAIN names ${open.join(", ")}, where anybody can register an address.\n` +
140
+ " That rule would let the public edit this site. Name the people instead:\n" +
141
+ " MICA_ADMIT_EMAILS=alice@gmail.com,bob@gmail.com");
142
+ }
143
+ const malformed = config.admitEmails.filter((email) => !/^[^@\s]+@[^@\s]+$/.test(email));
144
+ if (malformed.length > 0) {
145
+ throw new ConfigError(`MICA_ADMIT_EMAILS entries are email addresses, and these are not: ${malformed.join(", ")}`);
146
+ }
109
147
  const wrong = config.admitSubjects.filter((entry) => !entry.issuer || !entry.subject);
110
148
  if (wrong.length > 0) {
111
149
  throw new ConfigError("MICA_ADMIT_SUBJECTS entries are `issuer|subject` pairs, and one is not:\n" +
112
150
  ` ${wrong.map((entry) => entry.subject || "(empty)").join(", ")}\n\n` +
113
151
  " The issuer is part of the key because a subject is only unique within\n" +
114
- " the provider that issued it. It is never an email address: providers\n" +
115
- " let people change those, and two people can hold one in turn.\n" +
152
+ " the provider that issued it. To admit an address, use MICA_ADMIT_EMAILS.\n" +
116
153
  ` MICA_ADMIT_SUBJECTS=${config.MICA_OIDC_ISSUER ?? "https://id.example.com"}|a1b2c3`);
117
154
  }
118
155
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/gate.d.ts CHANGED
@@ -3,19 +3,18 @@
3
3
  *
4
4
  * Two layers, in order. `requireAccess` turns a session cookie into an
5
5
  * `AccessContext` — or a 401 — re-checking admission on every request so a
6
- * configuration change is immediate. `csrfProtection` is the double-submit
6
+ * configuration change is immediate, and re-issuing the cookie while the
7
+ * session is in use. `csrfProtection` is the double-submit
7
8
  * token over the Origin check the runtime already applies: cross-origin
8
9
  * JavaScript can neither read the cookie nor set the header.
9
10
  */
10
11
  import type { Context, MiddlewareHandler } from "hono";
11
12
  import { type Env } from "@micacms/runtime";
12
13
  import type { AdmissionPolicy } from "./admission.js";
13
- import { type SessionService } from "./session.js";
14
- import type { SessionRecord, SessionRepository } from "./sessions-repository.js";
14
+ import { type SessionRecord, type SessionService } from "./session.js";
15
15
  export declare function sessionOf(c: Context<Env>): SessionRecord;
16
16
  export interface GateDeps {
17
17
  sessions: SessionService;
18
- sessionsRepo: SessionRepository;
19
18
  admission: AdmissionPolicy;
20
19
  }
21
20
  /**
@@ -36,5 +35,5 @@ export declare function requireAccess(deps: GateDeps): MiddlewareHandler<Env>;
36
35
  * Runs after `requireAccess`, and complements the session cookie's
37
36
  * `SameSite=Lax`, which already blocks the classic cross-site form post.
38
37
  */
39
- export declare function csrfProtection(): MiddlewareHandler<Env>;
38
+ export declare function csrfProtection(deps: GateDeps): MiddlewareHandler<Env>;
40
39
  //# sourceMappingURL=gate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,EAAY,KAAK,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAuD,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACxG,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAQjF,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,aAAa,CAGxD;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,cAAc,CAAC;IACzB,YAAY,EAAE,iBAAiB,CAAC;IAChC,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAgCpE;AAID;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,iBAAiB,CAAC,GAAG,CAAC,CAiBvD"}
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,EAAY,KAAK,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAQtB,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,aAAa,CAGxD;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,cAAc,CAAC;IACzB,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAiCpE;AAID;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAiBrE"}
package/dist/gate.js CHANGED
@@ -3,13 +3,14 @@
3
3
  *
4
4
  * Two layers, in order. `requireAccess` turns a session cookie into an
5
5
  * `AccessContext` — or a 401 — re-checking admission on every request so a
6
- * configuration change is immediate. `csrfProtection` is the double-submit
6
+ * configuration change is immediate, and re-issuing the cookie while the
7
+ * session is in use. `csrfProtection` is the double-submit
7
8
  * token over the Origin check the runtime already applies: cross-origin
8
9
  * JavaScript can neither read the cookie nor set the header.
9
10
  */
10
- import { getCookie } from "hono/cookie";
11
+ import { getCookie, setCookie } from "hono/cookie";
11
12
  import { ApiError } from "@micacms/runtime";
12
- import { CSRF_COOKIE, CSRF_HEADER, safeEqual, SESSION_COOKIE } from "./session.js";
13
+ import { CSRF_HEADER, safeEqual, } from "./session.js";
13
14
  /**
14
15
  * The session behind the current request. Auth's own context variable —
15
16
  * the runtime knows only the `access` it derives from it.
@@ -31,18 +32,11 @@ export function sessionOf(c) {
31
32
  */
32
33
  export function requireAccess(deps) {
33
34
  return async (c, next) => {
34
- const session = deps.sessions.resolve(getCookie(c, SESSION_COOKIE));
35
+ let session = deps.sessions.resolve(getCookie(c, deps.sessions.names.session));
35
36
  if (!session) {
36
37
  throw new ApiError("unauthenticated", "Your session has expired. Please sign in again.");
37
38
  }
38
39
  const { identity } = session;
39
- if (!identity) {
40
- // A row from a pre-split local-mode run of the same database. It
41
- // authenticates nobody, so it is not a session for an authenticated
42
- // deployment.
43
- deps.sessionsRepo.delete(session.id);
44
- throw new ApiError("unauthenticated", "Please sign in again.");
45
- }
46
40
  const verdict = deps.admission.admits(identity);
47
41
  if (!verdict.admitted) {
48
42
  c.get("logger").warn("Session no longer admitted", {
@@ -51,11 +45,18 @@ export function requireAccess(deps) {
51
45
  });
52
46
  throw new ApiError("forbidden", "You no longer have access to this site.");
53
47
  }
48
+ const refreshed = deps.sessions.refresh(session);
49
+ if (refreshed)
50
+ session = refreshed.record;
54
51
  c.set(SESSION_VARIABLE, session);
55
52
  c.set("access", {
56
53
  identity: { subject: identity.subject, email: identity.email, name: identity.name },
57
54
  });
58
55
  await next();
56
+ // After the handler, so the cookie lands on whatever response it built.
57
+ if (refreshed) {
58
+ setCookie(c, deps.sessions.names.session, refreshed.cookie, deps.sessions.sessionCookieOptions());
59
+ }
59
60
  };
60
61
  }
61
62
  const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
@@ -66,7 +67,7 @@ const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
66
67
  * Runs after `requireAccess`, and complements the session cookie's
67
68
  * `SameSite=Lax`, which already blocks the classic cross-site form post.
68
69
  */
69
- export function csrfProtection() {
70
+ export function csrfProtection(deps) {
70
71
  return async (c, next) => {
71
72
  if (SAFE_METHODS.has(c.req.method)) {
72
73
  await next();
@@ -74,7 +75,7 @@ export function csrfProtection() {
74
75
  }
75
76
  const session = sessionOf(c);
76
77
  const header = c.req.header(CSRF_HEADER);
77
- const cookie = getCookie(c, CSRF_COOKIE);
78
+ const cookie = getCookie(c, deps.sessions.names.csrf);
78
79
  if (!header || !cookie || !safeEqual(header, cookie) || !safeEqual(header, session.csrfToken)) {
79
80
  throw new ApiError("forbidden", "Invalid or missing CSRF token. Reload the page and retry.");
80
81
  }
package/dist/gate.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,EAAE,QAAQ,EAAY,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAuB,MAAM,cAAc,CAAC;AAGxG;;;GAGG;AACH,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAEvC,MAAM,UAAU,SAAS,CAAC,CAAe;IACvC,2EAA2E;IAC3E,OAAO,CAAC,CAAC,GAAG,CAAC,gBAAyB,CAAkB,CAAC;AAC3D,CAAC;AAQD;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,IAAc;IAC1C,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;QAEpE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,QAAQ,CAAC,iBAAiB,EAAE,iDAAiD,CAAC,CAAC;QAC3F,CAAC;QAED,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;QAC7B,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,iEAAiE;YACjE,oEAAoE;YACpE,cAAc;YACd,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACrC,MAAM,IAAI,QAAQ,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;QACjE,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACtB,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,4BAA4B,EAAE;gBACjD,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC,CAAC;YACH,MAAM,IAAI,QAAQ,CAAC,WAAW,EAAE,yCAAyC,CAAC,CAAC;QAC7E,CAAC;QAED,CAAC,CAAC,GAAG,CAAC,gBAAyB,EAAE,OAAgB,CAAC,CAAC;QACnD,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;YACd,QAAQ,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE;SACpF,CAAC,CAAC;QACH,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;QAEzC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9F,MAAM,IAAI,QAAQ,CAAC,WAAW,EAAE,2DAA2D,CAAC,CAAC;QAC/F,CAAC;QAED,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @micacms/auth-oidc — the server-only OIDC gate around @micacms/runtime.
3
3
  *
4
- * One factory: hand it the finished configuration, an open database and a
5
- * logger, and it returns the `AuthSurface` the runtime mounts — sign-in
4
+ * One factory: hand it the finished configuration and a logger, and it
5
+ * returns the `AuthSurface` the runtime mounts — sign-in
6
6
  * routes at `/api/auth`, and the session-plus-CSRF gate in front of the admin
7
7
  * API — together with the services the composing app reports on.
8
8
  *
@@ -10,24 +10,20 @@
10
10
  * The local editor must never gain a path to it: that absence is the product
11
11
  * boundary that keeps `npx @micacms/editor` free of authentication code.
12
12
  */
13
- import type { AuthSurface, Logger, SqlDatabase } from "@micacms/runtime";
13
+ import type { AuthSurface, Logger } from "@micacms/runtime";
14
14
  import { type RateLimiter } from "@micacms/runtime";
15
15
  import { type AdmissionPolicy } from "./admission.js";
16
16
  import type { AuthConfig } from "./config.js";
17
17
  import { type OidcService } from "./oidc.js";
18
18
  import { type SessionService } from "./session.js";
19
- import { type SessionRepository } from "./sessions-repository.js";
20
19
  export interface OidcAuthDeps {
21
20
  config: AuthConfig;
22
- /** The database sessions live in. Owned and opened by the composing app. */
23
- db: SqlDatabase;
24
21
  logger: Logger;
25
22
  }
26
23
  export interface OidcAuth {
27
24
  /** What `createApp` mounts: routes at /api/auth, the gate before /api/admin. */
28
25
  surface: AuthSurface;
29
26
  sessions: SessionService;
30
- sessionsRepo: SessionRepository;
31
27
  admission: AdmissionPolicy;
32
28
  oidc: OidcService;
33
29
  loginLimiter: RateLimiter;
@@ -37,6 +33,5 @@ export { createAdmissionPolicy, type AdmissionPolicy, type AdmissionResult } fro
37
33
  export { authEnv, deriveAuth, requireAuthConfig, type AuthConfig, type AuthEnv, } from "./config.js";
38
34
  export { csrfProtection, requireAccess, sessionOf } from "./gate.js";
39
35
  export { createOidcService, type OidcService } from "./oidc.js";
40
- export { createSessionService, CSRF_COOKIE, CSRF_HEADER, SESSION_COOKIE, type SessionService, } from "./session.js";
41
- export { createSessionRepository, type SessionIdentity, type SessionRecord, type SessionRepository, } from "./sessions-repository.js";
36
+ export { cookieNames, createSessionService, CSRF_COOKIE, CSRF_HEADER, SESSION_COOKIE, type SessionIdentity, type SessionRecord, type SessionService, } from "./session.js";
42
37
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAEhE,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE3F,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,CAAC;IACnB,4EAA4E;IAC5E,EAAE,EAAE,WAAW,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,gFAAgF;IAChF,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,cAAc,CAAC;IACzB,YAAY,EAAE,iBAAiB,CAAC;IAChC,SAAS,EAAE,eAAe,CAAC;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,WAAW,CAAC;CAC3B;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAyB3D;AAED,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACnG,OAAO,EACL,OAAO,EACP,UAAU,EACV,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,OAAO,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,cAAc,EACd,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,uBAAuB,EACvB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACvB,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAEhE,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAEzE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,gFAAgF;IAChF,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,cAAc,CAAC;IACzB,SAAS,EAAE,eAAe,CAAC;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,WAAW,CAAC;CAC3B;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAuB3D;AAED,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACnG,OAAO,EACL,OAAO,EACP,UAAU,EACV,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,OAAO,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @micacms/auth-oidc — the server-only OIDC gate around @micacms/runtime.
3
3
  *
4
- * One factory: hand it the finished configuration, an open database and a
5
- * logger, and it returns the `AuthSurface` the runtime mounts — sign-in
4
+ * One factory: hand it the finished configuration and a logger, and it
5
+ * returns the `AuthSurface` the runtime mounts — sign-in
6
6
  * routes at `/api/auth`, and the session-plus-CSRF gate in front of the admin
7
7
  * API — together with the services the composing app reports on.
8
8
  *
@@ -16,25 +16,22 @@ import { csrfProtection, requireAccess } from "./gate.js";
16
16
  import { createOidcService } from "./oidc.js";
17
17
  import { authRoutes } from "./routes.js";
18
18
  import { createSessionService } from "./session.js";
19
- import { createSessionRepository } from "./sessions-repository.js";
20
19
  export function createOidcAuth(deps) {
21
20
  const { config, logger } = deps;
22
- const sessionsRepo = createSessionRepository(deps.db);
23
- const sessions = createSessionService(sessionsRepo, config);
21
+ const sessions = createSessionService(config);
24
22
  const admission = createAdmissionPolicy(config);
25
23
  const oidc = createOidcService({ config, logger });
26
24
  const loginLimiter = createRateLimiter({
27
25
  limit: config.LOGIN_MAX_ATTEMPTS,
28
26
  windowMs: config.LOGIN_WINDOW_MS,
29
27
  });
30
- const gate = { sessions, sessionsRepo, admission };
28
+ const gate = { sessions, admission };
31
29
  return {
32
30
  surface: {
33
- routes: authRoutes({ config, sessions, sessionsRepo, oidc, admission, loginLimiter }),
34
- gate: [requireAccess(gate), csrfProtection()],
31
+ routes: authRoutes({ config, sessions, oidc, admission, loginLimiter }),
32
+ gate: [requireAccess(gate), csrfProtection(gate)],
35
33
  },
36
34
  sessions,
37
- sessionsRepo,
38
35
  admission,
39
36
  oidc,
40
37
  loginLimiter,
@@ -44,6 +41,5 @@ export { createAdmissionPolicy } from "./admission.js";
44
41
  export { authEnv, deriveAuth, requireAuthConfig, } from "./config.js";
45
42
  export { csrfProtection, requireAccess, sessionOf } from "./gate.js";
46
43
  export { createOidcService } from "./oidc.js";
47
- export { createSessionService, CSRF_COOKIE, CSRF_HEADER, SESSION_COOKIE, } from "./session.js";
48
- export { createSessionRepository, } from "./sessions-repository.js";
44
+ export { cookieNames, createSessionService, CSRF_COOKIE, CSRF_HEADER, SESSION_COOKIE, } from "./session.js";
49
45
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,iBAAiB,EAAoB,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAwB,MAAM,gBAAgB,CAAC;AAE7E,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAoB,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAuB,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAA0B,MAAM,0BAA0B,CAAC;AAmB3F,MAAM,UAAU,cAAc,CAAC,IAAkB;IAC/C,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEhC,MAAM,YAAY,GAAG,uBAAuB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACnD,MAAM,YAAY,GAAG,iBAAiB,CAAC;QACrC,KAAK,EAAE,MAAM,CAAC,kBAAkB;QAChC,QAAQ,EAAE,MAAM,CAAC,eAAe;KACjC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;IAEnD,OAAO;QACL,OAAO,EAAE;YACP,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;YACrF,IAAI,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,CAAC;SAC9C;QACD,QAAQ;QACR,YAAY;QACZ,SAAS;QACT,IAAI;QACJ,YAAY;KACb,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,qBAAqB,EAA8C,MAAM,gBAAgB,CAAC;AACnG,OAAO,EACL,OAAO,EACP,UAAU,EACV,iBAAiB,GAGlB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAoB,MAAM,WAAW,CAAC;AAChE,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,cAAc,GAEf,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,uBAAuB,GAIxB,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,iBAAiB,EAAoB,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAwB,MAAM,gBAAgB,CAAC;AAE7E,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAoB,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAuB,MAAM,cAAc,CAAC;AAgBzE,MAAM,UAAU,cAAc,CAAC,IAAkB;IAC/C,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEhC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACnD,MAAM,YAAY,GAAG,iBAAiB,CAAC;QACrC,KAAK,EAAE,MAAM,CAAC,kBAAkB;QAChC,QAAQ,EAAE,MAAM,CAAC,eAAe;KACjC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAErC,OAAO;QACL,OAAO,EAAE;YACP,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;YACvE,IAAI,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC;SAClD;QACD,QAAQ;QACR,SAAS;QACT,IAAI;QACJ,YAAY;KACb,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,qBAAqB,EAA8C,MAAM,gBAAgB,CAAC;AACnG,OAAO,EACL,OAAO,EACP,UAAU,EACV,iBAAiB,GAGlB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAoB,MAAM,WAAW,CAAC;AAChE,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,cAAc,GAIf,MAAM,cAAc,CAAC"}
package/dist/oidc.d.ts CHANGED
@@ -26,8 +26,6 @@
26
26
  */
27
27
  import { type Logger } from "@micacms/runtime";
28
28
  import type { AuthConfig } from "./config.js";
29
- /** Carries state, PKCE verifier and nonce across the redirect to the provider. */
30
- export declare const TRANSACTION_COOKIE = "mica_oidc";
31
29
  export interface OidcTransaction {
32
30
  state: string;
33
31
  nonce: string;
@@ -49,6 +47,15 @@ export interface OidcClaims {
49
47
  }
50
48
  export declare function sealTransaction(transaction: OidcTransaction, secret: string): string;
51
49
  export declare function openTransaction(cookie: string, secret: string): OidcTransaction | null;
50
+ /**
51
+ * The address and its `email_verified` flag, always from the same answer, so
52
+ * that a flag cannot vouch for an address it was not sent with (#91). Userinfo
53
+ * fills in an address the ID token lacks, or confirms the token's own address.
54
+ */
55
+ export declare function emailFrom(claims: Record<string, unknown>, info?: Record<string, unknown>): {
56
+ email: string;
57
+ emailVerified: boolean;
58
+ };
52
59
  export interface OidcServiceDeps {
53
60
  config: AuthConfig;
54
61
  logger: Logger;
@@ -1 +1 @@
1
- {"version":3,"file":"oidc.d.ts","sourceRoot":"","sources":["../src/oidc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAMH,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAU9C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAgBD,wBAAgB,eAAe,CAAC,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGpF;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAkBtF;AAMD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe;IA2CnD,4EAA4E;oBACtD,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,eAAe,CAAA;KAAE,CAAC;IAwBrF;;;;;;;OAOG;yBACwB,GAAG,eAAe,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;IA+DlF;;;;;;OAMG;qBACoB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;EAahD;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAiB/D,2EAA2E;AAC3E,eAAO,MAAM,WAAW,QAAO,MAA+C,CAAC"}
1
+ {"version":3,"file":"oidc.d.ts","sourceRoot":"","sources":["../src/oidc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAMH,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAU9C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAoBD,wBAAgB,eAAe,CAAC,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGpF;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAkBtF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,CAU3C;AAMD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe;IA2CnD,4EAA4E;oBACtD,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,eAAe,CAAA;KAAE,CAAC;IAwBrF;;;;;;;OAOG;yBACwB,GAAG,eAAe,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;IA6DlF;;;;;;OAMG;qBACoB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;EAahD;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAiB/D,2EAA2E;AAC3E,eAAO,MAAM,WAAW,QAAO,MAA+C,CAAC"}