@wizeworks/lens-cloud 0.3.0-alpha.1 → 0.3.0-alpha.2

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 (275) hide show
  1. package/README.md +57 -5
  2. package/dist/alerts/evaluate.d.ts.map +1 -1
  3. package/dist/alerts/evaluate.js +17 -2
  4. package/dist/alerts/evaluate.js.map +1 -1
  5. package/dist/alerts/notify.d.ts +13 -2
  6. package/dist/alerts/notify.d.ts.map +1 -1
  7. package/dist/alerts/notify.js +22 -16
  8. package/dist/alerts/notify.js.map +1 -1
  9. package/dist/app.d.ts +10 -2
  10. package/dist/app.d.ts.map +1 -1
  11. package/dist/auth/auth.d.ts +51 -0
  12. package/dist/auth/auth.d.ts.map +1 -0
  13. package/dist/auth/auth.js +63 -0
  14. package/dist/auth/auth.js.map +1 -0
  15. package/dist/auth/bridge.d.ts +7 -0
  16. package/dist/auth/bridge.d.ts.map +1 -0
  17. package/dist/auth/bridge.js +39 -0
  18. package/dist/auth/bridge.js.map +1 -0
  19. package/dist/auth/crypto.d.ts +0 -5
  20. package/dist/auth/crypto.d.ts.map +1 -1
  21. package/dist/auth/crypto.js +3 -20
  22. package/dist/auth/crypto.js.map +1 -1
  23. package/dist/auth/delete-user.d.ts +16 -0
  24. package/dist/auth/delete-user.d.ts.map +1 -0
  25. package/dist/auth/delete-user.js +21 -0
  26. package/dist/auth/delete-user.js.map +1 -0
  27. package/dist/auth/deps.d.ts +27 -0
  28. package/dist/auth/deps.d.ts.map +1 -0
  29. package/dist/auth/deps.js +3 -0
  30. package/dist/auth/deps.js.map +1 -0
  31. package/dist/auth/email-token.d.ts +8 -0
  32. package/dist/auth/email-token.d.ts.map +1 -0
  33. package/dist/auth/email-token.js +15 -0
  34. package/dist/auth/email-token.js.map +1 -0
  35. package/dist/auth/hooks.d.ts +4 -0
  36. package/dist/auth/hooks.d.ts.map +1 -0
  37. package/dist/auth/hooks.js +143 -0
  38. package/dist/auth/hooks.js.map +1 -0
  39. package/dist/auth/options.d.ts +17 -0
  40. package/dist/auth/options.d.ts.map +1 -0
  41. package/dist/auth/options.js +98 -0
  42. package/dist/auth/options.js.map +1 -0
  43. package/dist/auth/password.d.ts +10 -0
  44. package/dist/auth/password.d.ts.map +1 -0
  45. package/dist/auth/password.js +22 -0
  46. package/dist/auth/password.js.map +1 -0
  47. package/dist/auth/plugins.d.ts +4 -0
  48. package/dist/auth/plugins.d.ts.map +1 -0
  49. package/dist/auth/plugins.js +45 -0
  50. package/dist/auth/plugins.js.map +1 -0
  51. package/dist/auth/principal.d.ts +8 -3
  52. package/dist/auth/principal.d.ts.map +1 -1
  53. package/dist/auth/principal.js +13 -8
  54. package/dist/auth/principal.js.map +1 -1
  55. package/dist/auth/roles.d.ts +43 -3
  56. package/dist/auth/roles.d.ts.map +1 -1
  57. package/dist/auth/roles.js +15 -6
  58. package/dist/auth/roles.js.map +1 -1
  59. package/dist/auth/schema-names.d.ts +130 -0
  60. package/dist/auth/schema-names.d.ts.map +1 -0
  61. package/dist/auth/schema-names.js +53 -0
  62. package/dist/auth/schema-names.js.map +1 -0
  63. package/dist/auth/secret.d.ts +12 -0
  64. package/dist/auth/secret.d.ts.map +1 -0
  65. package/dist/auth/secret.js +32 -0
  66. package/dist/auth/secret.js.map +1 -0
  67. package/dist/auth/security-notices.d.ts +34 -0
  68. package/dist/auth/security-notices.d.ts.map +1 -0
  69. package/dist/auth/security-notices.js +81 -0
  70. package/dist/auth/security-notices.js.map +1 -0
  71. package/dist/auth/signup.d.ts +23 -0
  72. package/dist/auth/signup.d.ts.map +1 -0
  73. package/dist/auth/signup.js +61 -0
  74. package/dist/auth/signup.js.map +1 -0
  75. package/dist/auth/sso.d.ts +21 -0
  76. package/dist/auth/sso.d.ts.map +1 -0
  77. package/dist/auth/sso.js +35 -0
  78. package/dist/auth/sso.js.map +1 -0
  79. package/dist/auth/wire.d.ts +16 -0
  80. package/dist/auth/wire.d.ts.map +1 -0
  81. package/dist/auth/wire.js +47 -0
  82. package/dist/auth/wire.js.map +1 -0
  83. package/dist/cli-args.d.ts +1 -1
  84. package/dist/cli-args.d.ts.map +1 -1
  85. package/dist/cli-args.js +55 -14
  86. package/dist/cli-args.js.map +1 -1
  87. package/dist/cli.js +18 -2
  88. package/dist/cli.js.map +1 -1
  89. package/dist/config.d.ts +26 -4
  90. package/dist/config.d.ts.map +1 -1
  91. package/dist/config.js +35 -0
  92. package/dist/config.js.map +1 -1
  93. package/dist/db/db.d.ts +5 -2
  94. package/dist/db/db.d.ts.map +1 -1
  95. package/dist/db/db.js +15 -8
  96. package/dist/db/db.js.map +1 -1
  97. package/dist/db/migrate.d.ts +2 -2
  98. package/dist/db/migrate.d.ts.map +1 -1
  99. package/dist/db/migrate.js +15 -5
  100. package/dist/db/migrate.js.map +1 -1
  101. package/dist/db/schema-auth.d.ts +2 -0
  102. package/dist/db/schema-auth.d.ts.map +1 -0
  103. package/dist/db/schema-auth.js +93 -0
  104. package/dist/db/schema-auth.js.map +1 -0
  105. package/dist/db/schema-mail.d.ts +2 -0
  106. package/dist/db/schema-mail.d.ts.map +1 -0
  107. package/dist/db/schema-mail.js +8 -0
  108. package/dist/db/schema-mail.js.map +1 -0
  109. package/dist/db/schema-notify.d.ts +2 -0
  110. package/dist/db/schema-notify.d.ts.map +1 -0
  111. package/dist/db/schema-notify.js +26 -0
  112. package/dist/db/schema-notify.js.map +1 -0
  113. package/dist/db/schema-sso.d.ts +2 -0
  114. package/dist/db/schema-sso.d.ts.map +1 -0
  115. package/dist/db/schema-sso.js +17 -0
  116. package/dist/db/schema-sso.js.map +1 -0
  117. package/dist/db/schema.d.ts.map +1 -1
  118. package/dist/db/schema.js +8 -0
  119. package/dist/db/schema.js.map +1 -1
  120. package/dist/http/http.d.ts +2 -0
  121. package/dist/http/http.d.ts.map +1 -1
  122. package/dist/http/http.js +7 -3
  123. package/dist/http/http.js.map +1 -1
  124. package/dist/jobs.d.ts +1 -1
  125. package/dist/jobs.d.ts.map +1 -1
  126. package/dist/jobs.js +14 -3
  127. package/dist/jobs.js.map +1 -1
  128. package/dist/mail/blocks.d.ts +14 -0
  129. package/dist/mail/blocks.d.ts.map +1 -0
  130. package/dist/mail/blocks.js +35 -0
  131. package/dist/mail/blocks.js.map +1 -0
  132. package/dist/mail/content.d.ts +68 -0
  133. package/dist/mail/content.d.ts.map +1 -0
  134. package/dist/mail/content.js +6 -0
  135. package/dist/mail/content.js.map +1 -0
  136. package/dist/mail/emails-digest.d.ts +10 -0
  137. package/dist/mail/emails-digest.d.ts.map +1 -0
  138. package/dist/mail/emails-digest.js +55 -0
  139. package/dist/mail/emails-digest.js.map +1 -0
  140. package/dist/mail/emails-health.d.ts +6 -0
  141. package/dist/mail/emails-health.d.ts.map +1 -0
  142. package/dist/mail/emails-health.js +36 -0
  143. package/dist/mail/emails-health.js.map +1 -0
  144. package/dist/mail/emails-security.d.ts +14 -0
  145. package/dist/mail/emails-security.d.ts.map +1 -0
  146. package/dist/mail/emails-security.js +27 -0
  147. package/dist/mail/emails-security.js.map +1 -0
  148. package/dist/mail/emails-team.d.ts +11 -0
  149. package/dist/mail/emails-team.d.ts.map +1 -0
  150. package/dist/mail/emails-team.js +45 -0
  151. package/dist/mail/emails-team.js.map +1 -0
  152. package/dist/mail/emails.d.ts +9 -0
  153. package/dist/mail/emails.d.ts.map +1 -0
  154. package/dist/mail/emails.js +49 -0
  155. package/dist/mail/emails.js.map +1 -0
  156. package/dist/mail/layout.d.ts +6 -0
  157. package/dist/mail/layout.d.ts.map +1 -0
  158. package/dist/mail/layout.js +10 -0
  159. package/dist/mail/layout.js.map +1 -0
  160. package/dist/mail/mailer.d.ts +25 -0
  161. package/dist/mail/mailer.d.ts.map +1 -0
  162. package/dist/mail/mailer.js +49 -0
  163. package/dist/mail/mailer.js.map +1 -0
  164. package/dist/mail/preview.d.ts +6 -0
  165. package/dist/mail/preview.d.ts.map +1 -0
  166. package/dist/mail/preview.js +26 -0
  167. package/dist/mail/preview.js.map +1 -0
  168. package/dist/mail/resend.d.ts +11 -0
  169. package/dist/mail/resend.d.ts.map +1 -0
  170. package/dist/mail/resend.js +34 -0
  171. package/dist/mail/resend.js.map +1 -0
  172. package/dist/mail/samples.d.ts +3 -0
  173. package/dist/mail/samples.d.ts.map +1 -0
  174. package/dist/mail/samples.js +63 -0
  175. package/dist/mail/samples.js.map +1 -0
  176. package/dist/mail/stop-route.d.ts +4 -0
  177. package/dist/mail/stop-route.d.ts.map +1 -0
  178. package/dist/mail/stop-route.js +41 -0
  179. package/dist/mail/stop-route.js.map +1 -0
  180. package/dist/mail/svix.d.ts +8 -0
  181. package/dist/mail/svix.d.ts.map +1 -0
  182. package/dist/mail/svix.js +17 -0
  183. package/dist/mail/svix.js.map +1 -0
  184. package/dist/mail/templates.d.ts +12 -0
  185. package/dist/mail/templates.d.ts.map +1 -0
  186. package/dist/mail/templates.js +18 -0
  187. package/dist/mail/templates.js.map +1 -0
  188. package/dist/mail/text.d.ts +4 -0
  189. package/dist/mail/text.d.ts.map +1 -0
  190. package/dist/mail/text.js +17 -0
  191. package/dist/mail/text.js.map +1 -0
  192. package/dist/mail/theme.d.ts +26 -0
  193. package/dist/mail/theme.d.ts.map +1 -0
  194. package/dist/mail/theme.js +21 -0
  195. package/dist/mail/theme.js.map +1 -0
  196. package/dist/mail/unsubscribe.d.ts +14 -0
  197. package/dist/mail/unsubscribe.d.ts.map +1 -0
  198. package/dist/mail/unsubscribe.js +39 -0
  199. package/dist/mail/unsubscribe.js.map +1 -0
  200. package/dist/mail/webhook.d.ts +5 -0
  201. package/dist/mail/webhook.d.ts.map +1 -0
  202. package/dist/mail/webhook.js +41 -0
  203. package/dist/mail/webhook.js.map +1 -0
  204. package/dist/notify/digest-data.d.ts +31 -0
  205. package/dist/notify/digest-data.d.ts.map +1 -0
  206. package/dist/notify/digest-data.js +37 -0
  207. package/dist/notify/digest-data.js.map +1 -0
  208. package/dist/notify/digest.d.ts +17 -0
  209. package/dist/notify/digest.d.ts.map +1 -0
  210. package/dist/notify/digest.js +76 -0
  211. package/dist/notify/digest.js.map +1 -0
  212. package/dist/notify/health.d.ts +6 -0
  213. package/dist/notify/health.d.ts.map +1 -0
  214. package/dist/notify/health.js +43 -0
  215. package/dist/notify/health.js.map +1 -0
  216. package/dist/notify/team-describe.d.ts +22 -0
  217. package/dist/notify/team-describe.d.ts.map +1 -0
  218. package/dist/notify/team-describe.js +67 -0
  219. package/dist/notify/team-describe.js.map +1 -0
  220. package/dist/notify/team.d.ts +6 -0
  221. package/dist/notify/team.d.ts.map +1 -0
  222. package/dist/notify/team.js +56 -0
  223. package/dist/notify/team.js.map +1 -0
  224. package/dist/repo/alerts.d.ts +6 -2
  225. package/dist/repo/alerts.d.ts.map +1 -1
  226. package/dist/repo/alerts.js.map +1 -1
  227. package/dist/repo/auth-prune.d.ts +4 -0
  228. package/dist/repo/auth-prune.d.ts.map +1 -0
  229. package/dist/repo/auth-prune.js +8 -0
  230. package/dist/repo/auth-prune.js.map +1 -0
  231. package/dist/repo/mail-prefs.d.ts +35 -0
  232. package/dist/repo/mail-prefs.d.ts.map +1 -0
  233. package/dist/repo/mail-prefs.js +34 -0
  234. package/dist/repo/mail-prefs.js.map +1 -0
  235. package/dist/repo/orgs.d.ts +15 -25
  236. package/dist/repo/orgs.d.ts.map +1 -1
  237. package/dist/repo/orgs.js +18 -60
  238. package/dist/repo/orgs.js.map +1 -1
  239. package/dist/repo/suppressions.d.ts +8 -0
  240. package/dist/repo/suppressions.d.ts.map +1 -0
  241. package/dist/repo/suppressions.js +16 -0
  242. package/dist/repo/suppressions.js.map +1 -0
  243. package/dist/repo/users.d.ts +5 -16
  244. package/dist/repo/users.d.ts.map +1 -1
  245. package/dist/repo/users.js +2 -60
  246. package/dist/repo/users.js.map +1 -1
  247. package/dist/routes/alerts.d.ts.map +1 -1
  248. package/dist/routes/alerts.js +23 -6
  249. package/dist/routes/alerts.js.map +1 -1
  250. package/dist/routes/index.d.ts.map +1 -1
  251. package/dist/routes/index.js +4 -2
  252. package/dist/routes/index.js.map +1 -1
  253. package/dist/routes/me.d.ts +4 -0
  254. package/dist/routes/me.d.ts.map +1 -0
  255. package/dist/routes/me.js +37 -0
  256. package/dist/routes/me.js.map +1 -0
  257. package/dist/routes/notify.d.ts +4 -0
  258. package/dist/routes/notify.d.ts.map +1 -0
  259. package/dist/routes/notify.js +55 -0
  260. package/dist/routes/notify.js.map +1 -0
  261. package/dist/routes/orgs.d.ts.map +1 -1
  262. package/dist/routes/orgs.js +30 -53
  263. package/dist/routes/orgs.js.map +1 -1
  264. package/dist/routes/projects.d.ts.map +1 -1
  265. package/dist/routes/projects.js +4 -3
  266. package/dist/routes/projects.js.map +1 -1
  267. package/dist/server.d.ts +2 -0
  268. package/dist/server.d.ts.map +1 -1
  269. package/dist/server.js +45 -14
  270. package/dist/server.js.map +1 -1
  271. package/package.json +19 -6
  272. package/dist/routes/auth.d.ts +0 -4
  273. package/dist/routes/auth.d.ts.map +0 -1
  274. package/dist/routes/auth.js +0 -106
  275. package/dist/routes/auth.js.map +0 -1
@@ -1,8 +1,48 @@
1
+ import { type OrgRole } from "@wizeworks/lens-core";
1
2
  /** Roles in an organization, from most to least power. */
2
3
  export declare const ROLES: readonly ["owner", "admin", "member", "viewer"];
3
- export type Role = (typeof ROLES)[number];
4
- export declare const isRole: (v: unknown) => v is Role;
5
- /** What each role may do. Read: data, members. Write: alerts, shares. Manage: projects, keys, members, settings. */
4
+ export type Role = OrgRole;
5
+ export declare const isRole: (v: unknown) => v is OrgRole;
6
+ /** Better Auth access control, built from the shared role data. */
7
+ export declare const ac: {
8
+ newRole<const TRoleStatements extends import("better-auth/plugins/access").Statements>(statements: import("better-auth/plugins/access").RoleInput<{
9
+ readonly organization: readonly ["update", "delete"];
10
+ readonly member: readonly ["create", "update", "delete"];
11
+ readonly invitation: readonly ["create", "cancel"];
12
+ readonly team: readonly ["create", "update", "delete"];
13
+ readonly ac: readonly ["create", "read", "update", "delete"];
14
+ readonly project: readonly ["read", "create", "update", "delete"];
15
+ readonly key: readonly ["read", "create", "revoke"];
16
+ readonly alert: readonly ["read", "create", "update", "delete"];
17
+ readonly share: readonly ["create", "revoke"];
18
+ readonly audit: readonly ["read"];
19
+ }, TRoleStatements>): import("better-auth/plugins/access").Role<import("better-auth/plugins/access").ExactRoleStatements<TRoleStatements>, {
20
+ readonly organization: readonly ["update", "delete"];
21
+ readonly member: readonly ["create", "update", "delete"];
22
+ readonly invitation: readonly ["create", "cancel"];
23
+ readonly team: readonly ["create", "update", "delete"];
24
+ readonly ac: readonly ["create", "read", "update", "delete"];
25
+ readonly project: readonly ["read", "create", "update", "delete"];
26
+ readonly key: readonly ["read", "create", "revoke"];
27
+ readonly alert: readonly ["read", "create", "update", "delete"];
28
+ readonly share: readonly ["create", "revoke"];
29
+ readonly audit: readonly ["read"];
30
+ }>;
31
+ statements: {
32
+ readonly organization: readonly ["update", "delete"];
33
+ readonly member: readonly ["create", "update", "delete"];
34
+ readonly invitation: readonly ["create", "cancel"];
35
+ readonly team: readonly ["create", "update", "delete"];
36
+ readonly ac: readonly ["create", "read", "update", "delete"];
37
+ readonly project: readonly ["read", "create", "update", "delete"];
38
+ readonly key: readonly ["read", "create", "revoke"];
39
+ readonly alert: readonly ["read", "create", "update", "delete"];
40
+ readonly share: readonly ["create", "revoke"];
41
+ readonly audit: readonly ["read"];
42
+ };
43
+ };
44
+ export declare const authRoles: Record<Role, ReturnType<typeof ac.newRole>>;
45
+ /** What our own routes check. Read: data, members. Write: alerts, shares. Manage: projects, keys, settings. Own: delete the organization. */
6
46
  export type Permission = "read" | "write" | "manage" | "own";
7
47
  export declare const can: (role: Role, permission: Permission) => boolean;
8
48
  /** An admin may not give a role above their own. */
@@ -1 +1 @@
1
- {"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../src/auth/roles.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,eAAO,MAAM,KAAK,YAAI,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AACrE,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1C,eAAO,MAAM,MAAM,MAAO,OAAO,KAAG,CAAC,IAAI,IAAyE,CAAC;AAEnH,oHAAoH;AACpH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAK7D,eAAO,MAAM,GAAG,SAAU,IAAI,cAAc,UAAU,KAAG,OAA2C,CAAC;AAErG,oDAAoD;AACpD,eAAO,MAAM,QAAQ,UAAW,IAAI,UAAU,IAAI,KAAG,OAAwC,CAAC"}
1
+ {"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../src/auth/roles.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6D,KAAK,OAAO,EAAsC,MAAM,sBAAsB,CAAC;AAGnJ,0DAA0D;AAC1D,eAAO,MAAM,KAAK,iDAAY,CAAC;AAC/B,MAAM,MAAM,IAAI,GAAG,OAAO,CAAC;AAC3B,eAAO,MAAM,MAAM,8BAAY,CAAC;AAEhC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAyC,CAAC;AACzD,eAAO,MAAM,SAAS,EAAgH,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;AAElL,6IAA6I;AAC7I,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAS7D,eAAO,MAAM,GAAG,SAAU,IAAI,cAAc,UAAU,KAAG,OAA8C,CAAC;AAExG,oDAAoD;AACpD,eAAO,MAAM,QAAQ,UAAW,IAAI,UAAU,IAAI,KAAG,OAAgD,CAAC"}
@@ -1,9 +1,18 @@
1
+ import { ACCESS_STATEMENTS, isOrgRole, ORG_ROLES, ROLE_GRANTS, ROLE_RANK, roleAllows } from "@wizeworks/lens-core";
2
+ import { createAccessControl } from "better-auth/plugins/access";
1
3
  /** Roles in an organization, from most to least power. */
2
- export const ROLES = ["owner", "admin", "member", "viewer"];
3
- export const isRole = (v) => typeof v === "string" && ROLES.includes(v);
4
- const LEVEL = { owner: 3, admin: 2, member: 1, viewer: 0 };
5
- const NEEDS = { read: 0, write: 1, manage: 2, own: 3 };
6
- export const can = (role, permission) => LEVEL[role] >= NEEDS[permission];
4
+ export const ROLES = ORG_ROLES;
5
+ export const isRole = isOrgRole;
6
+ /** Better Auth access control, built from the shared role data. */
7
+ export const ac = createAccessControl(ACCESS_STATEMENTS);
8
+ export const authRoles = Object.fromEntries(ROLES.map((r) => [r, ac.newRole(ROLE_GRANTS[r])]));
9
+ const NEEDS = {
10
+ read: { project: ["read"] },
11
+ write: { alert: ["create"], share: ["create"] },
12
+ manage: { project: ["create"], key: ["create"] },
13
+ own: { organization: ["delete"] },
14
+ };
15
+ export const can = (role, permission) => roleAllows(role, NEEDS[permission]);
7
16
  /** An admin may not give a role above their own. */
8
- export const canGrant = (actor, target) => LEVEL[actor] >= LEVEL[target];
17
+ export const canGrant = (actor, target) => ROLE_RANK[actor] >= ROLE_RANK[target];
9
18
  //# sourceMappingURL=roles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"roles.js","sourceRoot":"","sources":["../../src/auth/roles.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAGrE,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAU,EAAa,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAK,KAA2B,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAKnH,MAAM,KAAK,GAAyB,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AACjF,MAAM,KAAK,GAA+B,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AAEnF,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,IAAU,EAAE,UAAsB,EAAW,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC;AAErG,oDAAoD;AACpD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAW,EAAE,MAAY,EAAW,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"roles.js","sourceRoot":"","sources":["../../src/auth/roles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAoB,SAAS,EAAE,SAAS,EAAgB,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACnJ,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,KAAK,GAAG,SAAS,CAAC;AAE/B,MAAM,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC;AAEhC,mEAAmE;AACnE,MAAM,CAAC,MAAM,EAAE,GAAG,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;AACzD,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAqC,CAAC,CAAC,CAAC,CAAgD,CAAC;AAKlL,MAAM,KAAK,GAAoC;IAC7C,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE;IAC3B,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE;IAC/C,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE;IAChD,GAAG,EAAE,EAAE,YAAY,EAAE,CAAC,QAAQ,CAAC,EAAE;CAClC,CAAC;AAEF,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,IAAU,EAAE,UAAsB,EAAW,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;AAExG,oDAAoD;AACpD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAW,EAAE,MAAY,EAAW,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,130 @@
1
+ export declare const userNames: {
2
+ modelName: string;
3
+ fields: {
4
+ emailVerified: string;
5
+ createdAt: string;
6
+ updatedAt: string;
7
+ };
8
+ };
9
+ export declare const sessionNames: {
10
+ modelName: string;
11
+ fields: {
12
+ expiresAt: string;
13
+ createdAt: string;
14
+ updatedAt: string;
15
+ ipAddress: string;
16
+ userAgent: string;
17
+ userId: string;
18
+ };
19
+ };
20
+ export declare const accountNames: {
21
+ modelName: string;
22
+ fields: {
23
+ accountId: string;
24
+ providerId: string;
25
+ userId: string;
26
+ accessToken: string;
27
+ refreshToken: string;
28
+ idToken: string;
29
+ accessTokenExpiresAt: string;
30
+ refreshTokenExpiresAt: string;
31
+ createdAt: string;
32
+ updatedAt: string;
33
+ };
34
+ };
35
+ export declare const verificationNames: {
36
+ modelName: string;
37
+ fields: {
38
+ expiresAt: string;
39
+ createdAt: string;
40
+ updatedAt: string;
41
+ };
42
+ };
43
+ export declare const rateLimitNames: {
44
+ modelName: string;
45
+ fields: {
46
+ lastRequest: string;
47
+ };
48
+ };
49
+ export declare const orgSchema: {
50
+ readonly session: {
51
+ readonly fields: {
52
+ readonly activeOrganizationId: "active_org_id";
53
+ };
54
+ };
55
+ readonly organization: {
56
+ readonly modelName: "orgs";
57
+ readonly fields: {
58
+ readonly createdAt: "created_at";
59
+ };
60
+ readonly additionalFields: {
61
+ readonly retentionDays: {
62
+ readonly type: "number";
63
+ readonly required: true;
64
+ readonly defaultValue: 30;
65
+ readonly input: false;
66
+ readonly fieldName: "retention_days";
67
+ };
68
+ };
69
+ };
70
+ readonly member: {
71
+ readonly modelName: "members";
72
+ readonly fields: {
73
+ readonly organizationId: "org_id";
74
+ readonly userId: "user_id";
75
+ readonly createdAt: "created_at";
76
+ };
77
+ };
78
+ readonly invitation: {
79
+ readonly modelName: "invites";
80
+ readonly fields: {
81
+ readonly organizationId: "org_id";
82
+ readonly expiresAt: "expires_at";
83
+ readonly createdAt: "created_at";
84
+ readonly inviterId: "inviter_id";
85
+ };
86
+ };
87
+ };
88
+ export declare const twoFactorSchema: {
89
+ user: {
90
+ fields: {
91
+ twoFactorEnabled: string;
92
+ };
93
+ };
94
+ twoFactor: {
95
+ modelName: string;
96
+ fields: {
97
+ backupCodes: string;
98
+ userId: string;
99
+ failedVerificationCount: string;
100
+ lockedUntil: string;
101
+ };
102
+ };
103
+ };
104
+ export declare const passkeySchema: {
105
+ passkey: {
106
+ modelName: string;
107
+ fields: {
108
+ publicKey: string;
109
+ userId: string;
110
+ credentialID: string;
111
+ deviceType: string;
112
+ backedUp: string;
113
+ createdAt: string;
114
+ };
115
+ };
116
+ };
117
+ export declare const ssoSchema: {
118
+ ssoProvider: {
119
+ modelName: string;
120
+ fields: {
121
+ providerId: string;
122
+ oidcConfig: string;
123
+ samlConfig: string;
124
+ userId: string;
125
+ organizationId: string;
126
+ domainVerified: string;
127
+ };
128
+ };
129
+ };
130
+ //# sourceMappingURL=schema-names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-names.d.ts","sourceRoot":"","sources":["../../src/auth/schema-names.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,SAAS;IAAK,SAAS;IAAW,MAAM;QAAI,aAAa;QAAoB,SAAS;QAAgB,SAAS;;CAAkB,CAAC;AAE/I,eAAO,MAAM,YAAY;IACvB,SAAS;IACT,MAAM;QAAI,SAAS;QAAgB,SAAS;QAAgB,SAAS;QAAgB,SAAS;QAAgB,SAAS;QAAgB,MAAM;;CAC9I,CAAC;AAEF,eAAO,MAAM,YAAY;IACvB,SAAS;IACT,MAAM;QACJ,SAAS;QACT,UAAU;QACV,MAAM;QACN,WAAW;QACX,YAAY;QACZ,OAAO;QACP,oBAAoB;QACpB,qBAAqB;QACrB,SAAS;QACT,SAAS;;CAEZ,CAAC;AAEF,eAAO,MAAM,iBAAiB;IAAK,SAAS;IAAmB,MAAM;QAAI,SAAS;QAAgB,SAAS;QAAgB,SAAS;;CAAkB,CAAC;AAEvJ,eAAO,MAAM,cAAc;IAAK,SAAS;IAAiB,MAAM;QAAI,WAAW;;CAAoB,CAAC;AAEpG,eAAO,MAAM,SAAS;aACpB,OAAO;iBAAI,MAAM;qBAAI,oBAAoB,EAAE,eAAe;;;aAC1D,YAAY;iBACV,SAAS,EAAE,MAAM;iBACjB,MAAM;qBAAI,SAAS,EAAE,YAAY;;iBACjC,gBAAgB;qBAAI,aAAa;yBAAI,IAAI,EAAE,QAAQ;yBAAE,QAAQ;yBAAQ,YAAY,EAAE,EAAE;yBAAE,KAAK;yBAAS,SAAS,EAAE,gBAAgB;;;;aAElI,MAAM;iBAAI,SAAS,EAAE,SAAS;iBAAE,MAAM;qBAAI,cAAc,EAAE,QAAQ;qBAAE,MAAM,EAAE,SAAS;qBAAE,SAAS,EAAE,YAAY;;;aAC9G,UAAU;iBAAI,SAAS,EAAE,SAAS;iBAAE,MAAM;qBAAI,cAAc,EAAE,QAAQ;qBAAE,SAAS,EAAE,YAAY;qBAAE,SAAS,EAAE,YAAY;qBAAE,SAAS,EAAE,YAAY;;;CACzI,CAAC;AAEX,eAAO,MAAM,eAAe;IAC1B,IAAI;QAAI,MAAM;YAAI,gBAAgB;;;IAClC,SAAS;QACP,SAAS;QACT,MAAM;YAAI,WAAW;YAAkB,MAAM;YAAa,uBAAuB;YAA+B,WAAW;;;CAE9H,CAAC;AAEF,eAAO,MAAM,aAAa;IACxB,OAAO;QACL,SAAS;QACT,MAAM;YAAI,SAAS;YAAgB,MAAM;YAAa,YAAY;YAAmB,UAAU;YAAiB,QAAQ;YAAe,SAAS;;;CAEnJ,CAAC;AAEF,eAAO,MAAM,SAAS;IACpB,WAAW;QACT,SAAS;QACT,MAAM;YAAI,UAAU;YAAiB,UAAU;YAAiB,UAAU;YAAiB,MAAM;YAAa,cAAc;YAAY,cAAc;;;CAEzJ,CAAC"}
@@ -0,0 +1,53 @@
1
+ // Better Auth's models mapped onto our snake_case tables (db/schema-auth.ts). A test checks Better Auth finds nothing to change.
2
+ export const userNames = { modelName: "users", fields: { emailVerified: "email_verified", createdAt: "created_at", updatedAt: "updated_at" } };
3
+ export const sessionNames = {
4
+ modelName: "sessions",
5
+ fields: { expiresAt: "expires_at", createdAt: "created_at", updatedAt: "updated_at", ipAddress: "ip_address", userAgent: "user_agent", userId: "user_id" },
6
+ };
7
+ export const accountNames = {
8
+ modelName: "accounts",
9
+ fields: {
10
+ accountId: "account_id",
11
+ providerId: "provider_id",
12
+ userId: "user_id",
13
+ accessToken: "access_token",
14
+ refreshToken: "refresh_token",
15
+ idToken: "id_token",
16
+ accessTokenExpiresAt: "access_token_expires_at",
17
+ refreshTokenExpiresAt: "refresh_token_expires_at",
18
+ createdAt: "created_at",
19
+ updatedAt: "updated_at",
20
+ },
21
+ };
22
+ export const verificationNames = { modelName: "verifications", fields: { expiresAt: "expires_at", createdAt: "created_at", updatedAt: "updated_at" } };
23
+ export const rateLimitNames = { modelName: "rate_limits", fields: { lastRequest: "last_request" } };
24
+ export const orgSchema = {
25
+ session: { fields: { activeOrganizationId: "active_org_id" } },
26
+ organization: {
27
+ modelName: "orgs",
28
+ fields: { createdAt: "created_at" },
29
+ additionalFields: { retentionDays: { type: "number", required: true, defaultValue: 30, input: false, fieldName: "retention_days" } },
30
+ },
31
+ member: { modelName: "members", fields: { organizationId: "org_id", userId: "user_id", createdAt: "created_at" } },
32
+ invitation: { modelName: "invites", fields: { organizationId: "org_id", expiresAt: "expires_at", createdAt: "created_at", inviterId: "inviter_id" } },
33
+ };
34
+ export const twoFactorSchema = {
35
+ user: { fields: { twoFactorEnabled: "two_factor_enabled" } },
36
+ twoFactor: {
37
+ modelName: "two_factors",
38
+ fields: { backupCodes: "backup_codes", userId: "user_id", failedVerificationCount: "failed_verification_count", lockedUntil: "locked_until" },
39
+ },
40
+ };
41
+ export const passkeySchema = {
42
+ passkey: {
43
+ modelName: "passkeys",
44
+ fields: { publicKey: "public_key", userId: "user_id", credentialID: "credential_id", deviceType: "device_type", backedUp: "backed_up", createdAt: "created_at" },
45
+ },
46
+ };
47
+ export const ssoSchema = {
48
+ ssoProvider: {
49
+ modelName: "sso_providers",
50
+ fields: { providerId: "provider_id", oidcConfig: "oidc_config", samlConfig: "saml_config", userId: "user_id", organizationId: "org_id", domainVerified: "domain_verified" },
51
+ },
52
+ };
53
+ //# sourceMappingURL=schema-names.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-names.js","sourceRoot":"","sources":["../../src/auth/schema-names.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Db } from "../db/db.js";
2
+ export interface AuthSecret {
3
+ version: number;
4
+ value: string;
5
+ }
6
+ /** "value", or "2:new,1:old" to rotate (newest first). Rotating signs everyone out; encrypted 2FA data still opens with the old one. */
7
+ export declare function parseSecrets(text: string): AuthSecret[];
8
+ /** The configured secrets. A trial on PGlite without one gets a random secret kept in its own database; Postgres must be given one. */
9
+ export declare function resolveSecrets(db: Db, configured: string | undefined): Promise<AuthSecret[]>;
10
+ /** Better Auth reads these over its options; clear them so only our config counts. */
11
+ export declare function clearAuthEnv(env?: NodeJS.ProcessEnv): void;
12
+ //# sourceMappingURL=secret.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret.d.ts","sourceRoot":"","sources":["../../src/auth/secret.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAID,wIAAwI;AACxI,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAQvD;AAED,uIAAuI;AACvI,wBAAsB,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQlG;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CAEvE"}
@@ -0,0 +1,32 @@
1
+ import { randomBytes } from "node:crypto";
2
+ const MIN_LENGTH = 32;
3
+ /** "value", or "2:new,1:old" to rotate (newest first). Rotating signs everyone out; encrypted 2FA data still opens with the old one. */
4
+ export function parseSecrets(text) {
5
+ const parts = text.split(",").map((s) => s.trim()).filter(Boolean);
6
+ const secrets = parts.map((p, i) => {
7
+ const m = /^(\d+):(.+)$/.exec(p);
8
+ return m ? { version: Number(m[1]), value: m[2] } : { version: parts.length - i, value: p };
9
+ });
10
+ if (secrets.length === 0 || secrets.some((s) => s.value.length < MIN_LENGTH))
11
+ throw new Error(`Each auth secret needs at least ${MIN_LENGTH} characters (try: openssl rand -base64 32).`);
12
+ return secrets;
13
+ }
14
+ /** The configured secrets. A trial on PGlite without one gets a random secret kept in its own database; Postgres must be given one. */
15
+ export async function resolveSecrets(db, configured) {
16
+ if (configured)
17
+ return parseSecrets(configured);
18
+ if (db.kind === "postgres")
19
+ throw new Error("Set --auth-secret (or WIZELENS_CLOUD_AUTH_SECRET) to 32 or more random characters: it signs sessions and encrypts 2FA secrets.");
20
+ const [row] = await db.query("SELECT value FROM cloud_meta WHERE key = 'auth_secret'");
21
+ if (row)
22
+ return parseSecrets(row.value);
23
+ const value = randomBytes(36).toString("base64url");
24
+ await db.query("INSERT INTO cloud_meta (key, value) VALUES ('auth_secret', $1) ON CONFLICT (key) DO NOTHING", [value]);
25
+ return resolveSecrets(db, undefined);
26
+ }
27
+ /** Better Auth reads these over its options; clear them so only our config counts. */
28
+ export function clearAuthEnv(env = process.env) {
29
+ for (const k of ["BETTER_AUTH_TELEMETRY", "BETTER_AUTH_TELEMETRY_ENDPOINT", "BETTER_AUTH_TELEMETRY_DEBUG", "BETTER_AUTH_SECRET", "BETTER_AUTH_SECRETS", "AUTH_SECRET", "BETTER_AUTH_URL"])
30
+ delete env[k];
31
+ }
32
+ //# sourceMappingURL=secret.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret.js","sourceRoot":"","sources":["../../src/auth/secret.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ1C,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,wIAAwI;AACxI,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAC/F,CAAC,CAAC,CAAC;IACH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,UAAU,6CAA6C,CAAC,CAAC;IAC1L,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,uIAAuI;AACvI,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,EAAM,EAAE,UAA8B;IACzE,IAAI,UAAU;QAAE,OAAO,YAAY,CAAC,UAAU,CAAC,CAAC;IAChD,IAAI,EAAE,CAAC,IAAI,KAAK,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,gIAAgI,CAAC,CAAC;IAC9K,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE,CAAC,KAAK,CAAoB,wDAAwD,CAAC,CAAC;IAC1G,IAAI,GAAG;QAAE,OAAO,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,EAAE,CAAC,KAAK,CAAC,6FAA6F,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACvH,OAAO,cAAc,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;AACvC,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IAC/D,KAAK,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,gCAAgC,EAAE,6BAA6B,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,iBAAiB,CAAC;QAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;AAC3M,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { getSessionFromCtx } from "better-auth/api";
2
+ import type { BetterAuthOptions } from "better-auth";
3
+ import type { EmailContent } from "../mail/content.js";
4
+ import * as security from "../mail/emails-security.js";
5
+ import type { AuthDeps } from "./deps.js";
6
+ type DbHooks = NonNullable<BetterAuthOptions["databaseHooks"]>;
7
+ /** "Chrome on Windows". Versions are left out, so a browser update is not a new device. */
8
+ export declare function deviceOf(ua: string | null | undefined): string;
9
+ export declare const linksOf: (deps: Pick<AuthDeps, "publicUrl">) => security.SecurityLinks;
10
+ /** One security notice to the person's address now. */
11
+ export declare function sendSecurity(deps: AuthDeps, userId: string, content: (l: security.SecurityLinks) => EmailContent, to?: string): Promise<void>;
12
+ /** Database hooks for sessions and user changes: new devices, two-step sign-in on or off, and a changed email. */
13
+ export declare function securityDbHooks(deps: AuthDeps): Pick<DbHooks, "session" | "user">;
14
+ /** Before a passkey goes, keep its name for the notice. */
15
+ export declare function rememberPasskey(ctx: {
16
+ request?: Request;
17
+ body?: unknown;
18
+ context: {
19
+ adapter: {
20
+ findOne<T>(a: unknown): Promise<T | null>;
21
+ };
22
+ };
23
+ }): Promise<void>;
24
+ type AfterCtx = Parameters<typeof getSessionFromCtx>[0] & {
25
+ path: string;
26
+ request?: Request;
27
+ context: {
28
+ returned?: unknown;
29
+ };
30
+ };
31
+ /** Notices from endpoints: a changed password, a passkey added or removed, new backup codes. */
32
+ export declare function securityAfter(deps: AuthDeps, ctx: AfterCtx): Promise<void>;
33
+ export {};
34
+ //# sourceMappingURL=security-notices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-notices.d.ts","sourceRoot":"","sources":["../../src/auth/security-notices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,QAAQ,MAAM,4BAA4B,CAAC;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAG1C,KAAK,OAAO,GAAG,WAAW,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC,CAAC;AAM/D,2FAA2F;AAC3F,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAI9D;AAED,eAAO,MAAM,OAAO,SAAU,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,KAAG,QAAQ,CAAC,aAGnE,CAAC;AAEH,uDAAuD;AACvD,wBAAsB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,aAAa,KAAK,YAAY,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnJ;AAwBD,kHAAkH;AAClH,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,CAUjF;AAID,2DAA2D;AAC3D,wBAAsB,eAAe,CAAC,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE;QAAE,OAAO,EAAE;YAAE,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAKpK;AAED,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,CAAC;AAE/H,gGAAgG;AAChG,wBAAsB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhF"}
@@ -0,0 +1,81 @@
1
+ import { getSessionFromCtx } from "better-auth/api";
2
+ import * as security from "../mail/emails-security.js";
3
+ import { renderMail } from "../mail/templates.js";
4
+ import { changeOf } from "./email-token.js";
5
+ const BROWSERS = [[/Edg\//, "Edge"], [/OPR\//, "Opera"], [/Firefox\//, "Firefox"], [/Chrome\//, "Chrome"], [/Safari\//, "Safari"]];
6
+ const SYSTEMS = [[/iPhone|iPad/, "iOS"], [/Android/, "Android"], [/Windows/, "Windows"], [/Mac OS X|Macintosh/, "macOS"], [/CrOS/, "ChromeOS"], [/Linux/, "Linux"]];
7
+ /** "Chrome on Windows". Versions are left out, so a browser update is not a new device. */
8
+ export function deviceOf(ua) {
9
+ const browser = BROWSERS.find(([r]) => r.test(ua ?? ""))?.[1];
10
+ const system = SYSTEMS.find(([r]) => r.test(ua ?? ""))?.[1];
11
+ return browser && system ? `${browser} on ${system}` : browser || system || "an unknown device";
12
+ }
13
+ export const linksOf = (deps) => ({
14
+ account: `${deps.publicUrl}/_wizelens/#/account`,
15
+ forgot: `${deps.publicUrl}/_wizelens/#/forgot-password`,
16
+ });
17
+ /** One security notice to the person's address now. */
18
+ export async function sendSecurity(deps, userId, content, to) {
19
+ const [u] = to ? [{ email: to }] : await deps.db.query("SELECT email FROM users WHERE id = $1", [userId]);
20
+ if (u)
21
+ deps.mailer.send(renderMail(u.email, content(linksOf(deps))));
22
+ }
23
+ /** A sign-in on a browser and system this person never used gets a notice. The first device (sign-up) does not. */
24
+ async function onSession(deps, s) {
25
+ const device = deviceOf(s.userAgent);
26
+ const now = Date.now();
27
+ const fresh = await deps.db.query("INSERT INTO known_devices (user_id, device, first_ms, last_ms) VALUES ($1, $2, $3, $3) ON CONFLICT DO NOTHING RETURNING device", [s.userId, device, now]);
28
+ if (fresh.length === 0)
29
+ return void (await deps.db.query("UPDATE known_devices SET last_ms = $3 WHERE user_id = $1 AND device = $2", [s.userId, device, now]));
30
+ const [n] = await deps.db.query("SELECT count(*)::int AS n FROM known_devices WHERE user_id = $1", [s.userId]);
31
+ if ((n?.n ?? 0) > 1)
32
+ await sendSecurity(deps, s.userId, (l) => security.newSignIn(l, device, s.ipAddress ?? "", now));
33
+ }
34
+ /** What a user update is about to change, kept until it is saved. */
35
+ const pending = new WeakMap();
36
+ async function onUserUpdated(deps, user, ctx) {
37
+ const change = ctx ? pending.get(ctx) : undefined;
38
+ if (!change)
39
+ return;
40
+ const now = Date.now();
41
+ if (typeof change.twoFactorEnabled === "boolean")
42
+ await sendSecurity(deps, user.id, (l) => security.twoFactorChanged(l, change.twoFactorEnabled, now));
43
+ const moved = changeOf(ctx?.query?.token);
44
+ if (typeof change.email === "string" && moved && moved.from !== user.email)
45
+ await sendSecurity(deps, user.id, (l) => security.emailChanged(l, user.email, now), moved.from);
46
+ }
47
+ /** Database hooks for sessions and user changes: new devices, two-step sign-in on or off, and a changed email. */
48
+ export function securityDbHooks(deps) {
49
+ return {
50
+ session: { create: { after: async (s) => onSession(deps, s) } },
51
+ user: {
52
+ update: {
53
+ before: async (data, ctx) => void (ctx && pending.set(ctx, data)),
54
+ after: async (user, ctx) => onUserUpdated(deps, user, ctx),
55
+ },
56
+ },
57
+ };
58
+ }
59
+ const PASSKEY_NAMES = new WeakMap();
60
+ /** Before a passkey goes, keep its name for the notice. */
61
+ export async function rememberPasskey(ctx) {
62
+ const id = ctx.body?.id;
63
+ if (!ctx.request || typeof id !== "string")
64
+ return;
65
+ const row = await ctx.context.adapter.findOne({ model: "passkey", where: [{ field: "id", value: id }] });
66
+ PASSKEY_NAMES.set(ctx.request, row?.name ?? "");
67
+ }
68
+ /** Notices from endpoints: a changed password, a passkey added or removed, new backup codes. */
69
+ export async function securityAfter(deps, ctx) {
70
+ const notices = {
71
+ "/change-password": (l, now) => security.passwordChanged(l, now),
72
+ "/passkey/verify-registration": (l, now) => security.passkeyChanged(l, true, ctx.context.returned?.name ?? "", now),
73
+ "/passkey/delete-passkey": (l, now) => security.passkeyChanged(l, false, (ctx.request && PASSKEY_NAMES.get(ctx.request)) ?? "", now),
74
+ "/two-factor/generate-backup-codes": (l, now) => security.backupCodesMade(l, now),
75
+ };
76
+ const make = notices[ctx.path];
77
+ const user = make && (await getSessionFromCtx(ctx))?.user;
78
+ if (make && user)
79
+ await sendSecurity(deps, user.id, (l) => make(l, Date.now()));
80
+ }
81
+ //# sourceMappingURL=security-notices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-notices.js","sourceRoot":"","sources":["../../src/auth/security-notices.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type BetterAuthOptions } from "better-auth";
2
+ import type { AuthDeps } from "./deps.js";
3
+ type UserOptions = NonNullable<BetterAuthOptions["user"]>;
4
+ type Validate = NonNullable<UserOptions["validateUserInfo"]>;
5
+ export declare const SETUP_HEADER = "x-wizelens-setup-token";
6
+ export declare const INVITE_HEADER = "x-wizelens-invite";
7
+ /** Who may make an account: by SSO, only from a proven domain; the first needs the setup token; then the invited, or anyone with open sign-up. */
8
+ export declare function signupGate(deps: AuthDeps): Validate;
9
+ /** The provider proved the domain and the gate checked the email, so an SSO account starts confirmed. */
10
+ export declare function confirmSsoEmail<U extends {
11
+ email: string;
12
+ emailVerified?: boolean;
13
+ }>(user: U, ctx: unknown): Promise<{
14
+ data: U;
15
+ } | undefined>;
16
+ /** Runs after the new user is saved: the first account and open sign-ups get their own organization and project. */
17
+ export declare function afterSignup(deps: AuthDeps, createOrg: (userId: string, name: string) => Promise<unknown>): (user: {
18
+ id: string;
19
+ name: string;
20
+ email: string;
21
+ }, ctx: unknown) => Promise<void>;
22
+ export {};
23
+ //# sourceMappingURL=signup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signup.d.ts","sourceRoot":"","sources":["../../src/auth/signup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,iBAAiB,EAAqB,MAAM,aAAa,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAG1C,KAAK,WAAW,GAAG,WAAW,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;AAC1D,KAAK,QAAQ,GAAG,WAAW,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAG7D,eAAO,MAAM,YAAY,2BAA2B,CAAC;AACrD,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAmBjD,kJAAkJ;AAClJ,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAcnD;AAKD,yGAAyG;AACzG,wBAAsB,eAAe,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG,SAAS,CAAC,CAKnJ;AAED,oHAAoH;AACpH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,UACnF;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,OAAO,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC,CAW9F"}
@@ -0,0 +1,61 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import { getCurrentAdapter } from "better-auth";
3
+ import { ssoProviderOf, ssoTrusts } from "./sso.js";
4
+ export const SETUP_HEADER = "x-wizelens-setup-token";
5
+ export const INVITE_HEADER = "x-wizelens-invite";
6
+ const same = (a, b) => a.length === b.length && timingSafeEqual(Buffer.from(a), Buffer.from(b));
7
+ /** An open invite for this email (read through Better Auth's adapter, inside its transaction); without real mail, its id too. */
8
+ async function invitedNow(deps, ctx, email) {
9
+ const db = await getCurrentAdapter(ctx.context.adapter);
10
+ const rows = await db.findMany({ model: "invitation", where: [{ field: "email", value: email.toLowerCase() }, { field: "status", value: "pending" }] });
11
+ const open = rows.filter((r) => new Date(r.expiresAt).getTime() > Date.now());
12
+ if (deps.mailer.live)
13
+ return open.length > 0;
14
+ const id = ctx.headers?.get(INVITE_HEADER) ?? "";
15
+ return open.some((r) => same(r.id, id));
16
+ }
17
+ /** Who may make an account: by SSO, only from a proven domain; the first needs the setup token; then the invited, or anyone with open sign-up. */
18
+ export function signupGate(deps) {
19
+ return async ({ user, source }, ctx) => {
20
+ if (source.action !== "create-user")
21
+ return;
22
+ const db = await getCurrentAdapter(ctx.context.adapter);
23
+ const viaSso = ssoProviderOf(source);
24
+ if (viaSso)
25
+ return (await ssoTrusts(db, viaSso, String(user.email ?? ""))) ? undefined : { error: "sso_domain", errorDescription: "Single sign-on works only for emails at the provider's verified domain." };
26
+ if ((await db.count({ model: "user" })) === 0) {
27
+ const want = deps.setup.token();
28
+ if (want && same(ctx.headers?.get(SETUP_HEADER) ?? "", want))
29
+ return;
30
+ return { error: "setup_token", errorDescription: "The first account needs the setup link printed when the cloud started." };
31
+ }
32
+ if (deps.signup === "open" || (await invitedNow(deps, ctx, String(user.email ?? ""))))
33
+ return;
34
+ return { error: "signup_closed", errorDescription: "Sign-up needs an invite. Ask your team for one." };
35
+ };
36
+ }
37
+ /** A new account from single sign-on: the provider's organization takes it in. */
38
+ const bySso = (ctx) => String(ctx?.path ?? "").startsWith("/sso/");
39
+ /** The provider proved the domain and the gate checked the email, so an SSO account starts confirmed. */
40
+ export async function confirmSsoEmail(user, ctx) {
41
+ const providerId = ctx?.params?.providerId;
42
+ if (!bySso(ctx) || typeof providerId !== "string")
43
+ return undefined;
44
+ const db = await getCurrentAdapter(ctx.context.adapter);
45
+ return (await ssoTrusts(db, providerId, user.email)) ? { data: { ...user, emailVerified: true } } : undefined;
46
+ }
47
+ /** Runs after the new user is saved: the first account and open sign-ups get their own organization and project. */
48
+ export function afterSignup(deps, createOrg) {
49
+ return async (user, ctx) => {
50
+ if (!ctx || bySso(ctx))
51
+ return;
52
+ const [count] = await deps.db.query("SELECT count(*)::int AS n FROM users");
53
+ const [pending] = await deps.db.query("SELECT EXISTS (SELECT 1 FROM invites WHERE email = $1 AND status = 'pending' AND expires_at > now()) AS invited", [user.email.toLowerCase()]);
54
+ const first = count?.n === 1;
55
+ if (first)
56
+ deps.setup.done();
57
+ if (first || (deps.signup === "open" && !pending?.invited))
58
+ await createOrg(user.id, `${user.name.trim()}'s team`);
59
+ };
60
+ }
61
+ //# sourceMappingURL=signup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signup.js","sourceRoot":"","sources":["../../src/auth/signup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { BetterAuthPlugin } from "better-auth";
2
+ import type { AuthDeps } from "./deps.js";
3
+ /** The DNS record that proves a domain: `_wizelens-sso-<providerId>.<domain>` TXT `<token>`. */
4
+ export declare const SSO_TOKEN_PREFIX = "wizelens-sso";
5
+ interface Finder {
6
+ findOne<T>(a: {
7
+ model: string;
8
+ where: {
9
+ field: string;
10
+ value: string;
11
+ }[];
12
+ }): Promise<T | null>;
13
+ }
14
+ /** The SSO provider behind a new account, when Better Auth makes it from a single sign-on. */
15
+ export declare function ssoProviderOf(source: unknown): string | undefined;
16
+ /** True when an organization's provider has a proven domain and the email is at exactly that domain. */
17
+ export declare function ssoTrusts(db: Finder, providerId: string, email: string): Promise<boolean>;
18
+ /** Organization SSO with OIDC or SAML. Sign-in works once the domain is proven in DNS; new people join that organization as members. */
19
+ export declare function ssoPlugin(deps: AuthDeps): BetterAuthPlugin;
20
+ export {};
21
+ //# sourceMappingURL=sso.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sso.d.ts","sourceRoot":"","sources":["../../src/auth/sso.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAG1C,gGAAgG;AAChG,eAAO,MAAM,gBAAgB,iBAAiB,CAAC;AAE/C,UAAU,MAAM;IACd,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;CAChG;AAQD,8FAA8F;AAC9F,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGjE;AAED,wGAAwG;AACxG,wBAAsB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAK/F;AAED,wIAAwI;AACxI,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,gBAAgB,CAc1D"}