@dbx-tools/email 0.6.41 → 0.6.42

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/index.ts CHANGED
@@ -2,6 +2,10 @@
2
2
  // Regenerated from the exporting modules in ./src.
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
 
5
+ export * as authAllowlist from "./src/auth/allowlist.ts";
6
+ export * as authGate from "./src/auth/gate.ts";
7
+ export * as authOtp from "./src/auth/otp.ts";
8
+ export * as authRateLimit from "./src/auth/rate-limit.ts";
5
9
  export * as brand from "./src/brand.ts";
6
10
  export * as config from "./src/config.ts";
7
11
  export * as defaults from "./src/defaults.ts";
@@ -12,10 +16,15 @@ export * as plugin from "./src/plugin.ts";
12
16
  export * as sender from "./src/sender.ts";
13
17
  export * as tool from "./src/tool.ts";
14
18
  export * as transport from "./src/transport.ts";
19
+ export { SESSION_COOKIE, AuthGate } from "./src/auth/gate.ts";
20
+ export type { AuthGateOptions } from "./src/auth/gate.ts";
21
+ export { CodeStore } from "./src/auth/otp.ts";
22
+ export type { VerifyOutcome } from "./src/auth/otp.ts";
23
+ export { RateLimiter } from "./src/auth/rate-limit.ts";
15
24
  export { defaultEmailBrand } from "./src/brand.ts";
16
25
  export type { EmailBrand } from "./src/brand.ts";
17
26
  export { DEFAULT_SMTP_PORT, IMPLICIT_TLS_SMTP_PORT, EMAIL_CONFIG_SCHEMA } from "./src/config.ts";
18
- export type { SenderPolicy, SmtpConfig, EmailPluginConfig, ResolvedSmtpConfig, ResolvedFileConfig, ResolvedEmailConfig } from "./src/config.ts";
27
+ export type { SenderPolicy, SmtpConfig, EmailPluginConfig, AuthConfig, ResolvedSmtpConfig, ResolvedFileConfig, ResolvedEmailConfig, ResolvedAuthConfig } from "./src/config.ts";
19
28
  export { SEND_TIMEOUT_MS, VERIFY_TIMEOUT_MS, VERIFY_ATTEMPTS, MAX_ATTACHMENT_BYTES, MAX_ATTACHMENTS_TOTAL_BYTES, MAX_ATTACHMENT_COUNT, MAX_BODY_CHARS, EMAIL_SEND_SETTINGS, EMAIL_VERIFY_SETTINGS, EMAIL_SENDERS_SETTINGS } from "./src/defaults.ts";
20
29
  export type { EmailExecuteConfig, EmailExecutionSettings } from "./src/defaults.ts";
21
30
  export { escapeHtml } from "./src/email-html.ts";
package/lib/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ export * as authAllowlist from "./src/auth/allowlist.ts";
2
+ export * as authGate from "./src/auth/gate.ts";
3
+ export * as authOtp from "./src/auth/otp.ts";
4
+ export * as authRateLimit from "./src/auth/rate-limit.ts";
1
5
  export * as brand from "./src/brand.ts";
2
6
  export * as config from "./src/config.ts";
3
7
  export * as defaults from "./src/defaults.ts";
@@ -8,10 +12,15 @@ export * as plugin from "./src/plugin.ts";
8
12
  export * as sender from "./src/sender.ts";
9
13
  export * as tool from "./src/tool.ts";
10
14
  export * as transport from "./src/transport.ts";
15
+ export { SESSION_COOKIE, AuthGate } from "./src/auth/gate.ts";
16
+ export type { AuthGateOptions } from "./src/auth/gate.ts";
17
+ export { CodeStore } from "./src/auth/otp.ts";
18
+ export type { VerifyOutcome } from "./src/auth/otp.ts";
19
+ export { RateLimiter } from "./src/auth/rate-limit.ts";
11
20
  export { defaultEmailBrand } from "./src/brand.ts";
12
21
  export type { EmailBrand } from "./src/brand.ts";
13
22
  export { DEFAULT_SMTP_PORT, IMPLICIT_TLS_SMTP_PORT, EMAIL_CONFIG_SCHEMA } from "./src/config.ts";
14
- export type { SenderPolicy, SmtpConfig, EmailPluginConfig, ResolvedSmtpConfig, ResolvedFileConfig, ResolvedEmailConfig } from "./src/config.ts";
23
+ export type { SenderPolicy, SmtpConfig, EmailPluginConfig, AuthConfig, ResolvedSmtpConfig, ResolvedFileConfig, ResolvedEmailConfig, ResolvedAuthConfig } from "./src/config.ts";
15
24
  export { SEND_TIMEOUT_MS, VERIFY_TIMEOUT_MS, VERIFY_ATTEMPTS, MAX_ATTACHMENT_BYTES, MAX_ATTACHMENTS_TOTAL_BYTES, MAX_ATTACHMENT_COUNT, MAX_BODY_CHARS, EMAIL_SEND_SETTINGS, EMAIL_VERIFY_SETTINGS, EMAIL_SENDERS_SETTINGS } from "./src/defaults.ts";
16
25
  export type { EmailExecuteConfig, EmailExecutionSettings } from "./src/defaults.ts";
17
26
  export { escapeHtml } from "./src/email-html.ts";
package/lib/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  // GENERATED by projen watch - DO NOT EDIT.
2
2
  // Regenerated from the exporting modules in ./src.
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
+ export * as authAllowlist from "./src/auth/allowlist.js";
5
+ export * as authGate from "./src/auth/gate.js";
6
+ export * as authOtp from "./src/auth/otp.js";
7
+ export * as authRateLimit from "./src/auth/rate-limit.js";
4
8
  export * as brand from "./src/brand.js";
5
9
  export * as config from "./src/config.js";
6
10
  export * as defaults from "./src/defaults.js";
@@ -11,10 +15,13 @@ export * as plugin from "./src/plugin.js";
11
15
  export * as sender from "./src/sender.js";
12
16
  export * as tool from "./src/tool.js";
13
17
  export * as transport from "./src/transport.js";
18
+ export { SESSION_COOKIE, AuthGate } from "./src/auth/gate.js";
19
+ export { CodeStore } from "./src/auth/otp.js";
20
+ export { RateLimiter } from "./src/auth/rate-limit.js";
14
21
  export { defaultEmailBrand } from "./src/brand.js";
15
22
  export { DEFAULT_SMTP_PORT, IMPLICIT_TLS_SMTP_PORT, EMAIL_CONFIG_SCHEMA } from "./src/config.js";
16
23
  export { SEND_TIMEOUT_MS, VERIFY_TIMEOUT_MS, VERIFY_ATTEMPTS, MAX_ATTACHMENT_BYTES, MAX_ATTACHMENTS_TOTAL_BYTES, MAX_ATTACHMENT_COUNT, MAX_BODY_CHARS, EMAIL_SEND_SETTINGS, EMAIL_VERIFY_SETTINGS, EMAIL_SENDERS_SETTINGS } from "./src/defaults.js";
17
24
  export { escapeHtml } from "./src/email-html.js";
18
25
  export { EmailPlugin, email } from "./src/plugin.js";
19
26
  export { SEND_EMAIL_DESCRIPTION } from "./src/tool.js";
20
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sS0FBSyxNQUFNLE1BQU0saUJBQWlCLENBQUM7QUFDMUMsT0FBTyxLQUFLLFFBQVEsTUFBTSxtQkFBbUIsQ0FBQztBQUM5QyxPQUFPLEtBQUssU0FBUyxNQUFNLHFCQUFxQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxRQUFRLE1BQU0sbUJBQW1CLENBQUM7QUFDOUMsT0FBTyxLQUFLLE1BQU0sTUFBTSxpQkFBaUIsQ0FBQztBQUMxQyxPQUFPLEtBQUssTUFBTSxNQUFNLGlCQUFpQixDQUFDO0FBQzFDLE9BQU8sS0FBSyxNQUFNLE1BQU0saUJBQWlCLENBQUM7QUFDMUMsT0FBTyxLQUFLLElBQUksTUFBTSxlQUFlLENBQUM7QUFDdEMsT0FBTyxLQUFLLFNBQVMsTUFBTSxvQkFBb0IsQ0FBQztBQUNoRCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVuRCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsc0JBQXNCLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUVqRyxPQUFPLEVBQUUsZUFBZSxFQUFFLGlCQUFpQixFQUFFLGVBQWUsRUFBRSxvQkFBb0IsRUFBRSwyQkFBMkIsRUFBRSxvQkFBb0IsRUFBRSxjQUFjLEVBQUUsbUJBQW1CLEVBQUUscUJBQXFCLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUVyUCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFakQsT0FBTyxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxlQUFlLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBHRU5FUkFURUQgYnkgcHJvamVuIHdhdGNoIC0gRE8gTk9UIEVESVQuXG4vLyBSZWdlbmVyYXRlZCBmcm9tIHRoZSBleHBvcnRpbmcgbW9kdWxlcyBpbiAuL3NyYy5cbi8vIEhhbmQgZWRpdHMgYXJlIG92ZXJ3cml0dGVuIG9uIHRoZSBuZXh0IHdhdGNoOyB0aGlzIGZpbGUgaXMgcmVhZC1vbmx5LlxuXG5leHBvcnQgKiBhcyBicmFuZCBmcm9tIFwiLi9zcmMvYnJhbmQudHNcIjtcbmV4cG9ydCAqIGFzIGNvbmZpZyBmcm9tIFwiLi9zcmMvY29uZmlnLnRzXCI7XG5leHBvcnQgKiBhcyBkZWZhdWx0cyBmcm9tIFwiLi9zcmMvZGVmYXVsdHMudHNcIjtcbmV4cG9ydCAqIGFzIGVtYWlsSHRtbCBmcm9tIFwiLi9zcmMvZW1haWwtaHRtbC50c1wiO1xuZXhwb3J0ICogYXMgbWFya2Rvd24gZnJvbSBcIi4vc3JjL21hcmtkb3duLnRzXCI7XG5leHBvcnQgKiBhcyBvdXRib3ggZnJvbSBcIi4vc3JjL291dGJveC50c1wiO1xuZXhwb3J0ICogYXMgcGx1Z2luIGZyb20gXCIuL3NyYy9wbHVnaW4udHNcIjtcbmV4cG9ydCAqIGFzIHNlbmRlciBmcm9tIFwiLi9zcmMvc2VuZGVyLnRzXCI7XG5leHBvcnQgKiBhcyB0b29sIGZyb20gXCIuL3NyYy90b29sLnRzXCI7XG5leHBvcnQgKiBhcyB0cmFuc3BvcnQgZnJvbSBcIi4vc3JjL3RyYW5zcG9ydC50c1wiO1xuZXhwb3J0IHsgZGVmYXVsdEVtYWlsQnJhbmQgfSBmcm9tIFwiLi9zcmMvYnJhbmQudHNcIjtcbmV4cG9ydCB0eXBlIHsgRW1haWxCcmFuZCB9IGZyb20gXCIuL3NyYy9icmFuZC50c1wiO1xuZXhwb3J0IHsgREVGQVVMVF9TTVRQX1BPUlQsIElNUExJQ0lUX1RMU19TTVRQX1BPUlQsIEVNQUlMX0NPTkZJR19TQ0hFTUEgfSBmcm9tIFwiLi9zcmMvY29uZmlnLnRzXCI7XG5leHBvcnQgdHlwZSB7IFNlbmRlclBvbGljeSwgU210cENvbmZpZywgRW1haWxQbHVnaW5Db25maWcsIFJlc29sdmVkU210cENvbmZpZywgUmVzb2x2ZWRGaWxlQ29uZmlnLCBSZXNvbHZlZEVtYWlsQ29uZmlnIH0gZnJvbSBcIi4vc3JjL2NvbmZpZy50c1wiO1xuZXhwb3J0IHsgU0VORF9USU1FT1VUX01TLCBWRVJJRllfVElNRU9VVF9NUywgVkVSSUZZX0FUVEVNUFRTLCBNQVhfQVRUQUNITUVOVF9CWVRFUywgTUFYX0FUVEFDSE1FTlRTX1RPVEFMX0JZVEVTLCBNQVhfQVRUQUNITUVOVF9DT1VOVCwgTUFYX0JPRFlfQ0hBUlMsIEVNQUlMX1NFTkRfU0VUVElOR1MsIEVNQUlMX1ZFUklGWV9TRVRUSU5HUywgRU1BSUxfU0VOREVSU19TRVRUSU5HUyB9IGZyb20gXCIuL3NyYy9kZWZhdWx0cy50c1wiO1xuZXhwb3J0IHR5cGUgeyBFbWFpbEV4ZWN1dGVDb25maWcsIEVtYWlsRXhlY3V0aW9uU2V0dGluZ3MgfSBmcm9tIFwiLi9zcmMvZGVmYXVsdHMudHNcIjtcbmV4cG9ydCB7IGVzY2FwZUh0bWwgfSBmcm9tIFwiLi9zcmMvZW1haWwtaHRtbC50c1wiO1xuZXhwb3J0IHR5cGUgeyBFbWFpbEh0bWxPcHRpb25zIH0gZnJvbSBcIi4vc3JjL2VtYWlsLWh0bWwudHNcIjtcbmV4cG9ydCB7IEVtYWlsUGx1Z2luLCBlbWFpbCB9IGZyb20gXCIuL3NyYy9wbHVnaW4udHNcIjtcbmV4cG9ydCB7IFNFTkRfRU1BSUxfREVTQ1JJUFRJT04gfSBmcm9tIFwiLi9zcmMvdG9vbC50c1wiO1xuZXhwb3J0IHR5cGUgeyBFbWFpbFRvb2xPcHRpb25zIH0gZnJvbSBcIi4vc3JjL3Rvb2wudHNcIjtcbmV4cG9ydCB0eXBlIHsgRW1haWxFeGVjdXRvciwgRW1haWxSdW50aW1lIH0gZnJvbSBcIi4vc3JjL3RyYW5zcG9ydC50c1wiO1xuIl19
27
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssYUFBYSxNQUFNLHlCQUF5QixDQUFDO0FBQ3pELE9BQU8sS0FBSyxRQUFRLE1BQU0sb0JBQW9CLENBQUM7QUFDL0MsT0FBTyxLQUFLLE9BQU8sTUFBTSxtQkFBbUIsQ0FBQztBQUM3QyxPQUFPLEtBQUssYUFBYSxNQUFNLDBCQUEwQixDQUFDO0FBQzFELE9BQU8sS0FBSyxLQUFLLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEMsT0FBTyxLQUFLLE1BQU0sTUFBTSxpQkFBaUIsQ0FBQztBQUMxQyxPQUFPLEtBQUssUUFBUSxNQUFNLG1CQUFtQixDQUFDO0FBQzlDLE9BQU8sS0FBSyxTQUFTLE1BQU0scUJBQXFCLENBQUM7QUFDakQsT0FBTyxLQUFLLFFBQVEsTUFBTSxtQkFBbUIsQ0FBQztBQUM5QyxPQUFPLEtBQUssTUFBTSxNQUFNLGlCQUFpQixDQUFDO0FBQzFDLE9BQU8sS0FBSyxNQUFNLE1BQU0saUJBQWlCLENBQUM7QUFDMUMsT0FBTyxLQUFLLE1BQU0sTUFBTSxpQkFBaUIsQ0FBQztBQUMxQyxPQUFPLEtBQUssSUFBSSxNQUFNLGVBQWUsQ0FBQztBQUN0QyxPQUFPLEtBQUssU0FBUyxNQUFNLG9CQUFvQixDQUFDO0FBQ2hELE9BQU8sRUFBRSxjQUFjLEVBQUUsUUFBUSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFOUQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBRTlDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVuRCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsc0JBQXNCLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUVqRyxPQUFPLEVBQUUsZUFBZSxFQUFFLGlCQUFpQixFQUFFLGVBQWUsRUFBRSxvQkFBb0IsRUFBRSwyQkFBMkIsRUFBRSxvQkFBb0IsRUFBRSxjQUFjLEVBQUUsbUJBQW1CLEVBQUUscUJBQXFCLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUVyUCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFakQsT0FBTyxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxlQUFlLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBHRU5FUkFURUQgYnkgcHJvamVuIHdhdGNoIC0gRE8gTk9UIEVESVQuXG4vLyBSZWdlbmVyYXRlZCBmcm9tIHRoZSBleHBvcnRpbmcgbW9kdWxlcyBpbiAuL3NyYy5cbi8vIEhhbmQgZWRpdHMgYXJlIG92ZXJ3cml0dGVuIG9uIHRoZSBuZXh0IHdhdGNoOyB0aGlzIGZpbGUgaXMgcmVhZC1vbmx5LlxuXG5leHBvcnQgKiBhcyBhdXRoQWxsb3dsaXN0IGZyb20gXCIuL3NyYy9hdXRoL2FsbG93bGlzdC50c1wiO1xuZXhwb3J0ICogYXMgYXV0aEdhdGUgZnJvbSBcIi4vc3JjL2F1dGgvZ2F0ZS50c1wiO1xuZXhwb3J0ICogYXMgYXV0aE90cCBmcm9tIFwiLi9zcmMvYXV0aC9vdHAudHNcIjtcbmV4cG9ydCAqIGFzIGF1dGhSYXRlTGltaXQgZnJvbSBcIi4vc3JjL2F1dGgvcmF0ZS1saW1pdC50c1wiO1xuZXhwb3J0ICogYXMgYnJhbmQgZnJvbSBcIi4vc3JjL2JyYW5kLnRzXCI7XG5leHBvcnQgKiBhcyBjb25maWcgZnJvbSBcIi4vc3JjL2NvbmZpZy50c1wiO1xuZXhwb3J0ICogYXMgZGVmYXVsdHMgZnJvbSBcIi4vc3JjL2RlZmF1bHRzLnRzXCI7XG5leHBvcnQgKiBhcyBlbWFpbEh0bWwgZnJvbSBcIi4vc3JjL2VtYWlsLWh0bWwudHNcIjtcbmV4cG9ydCAqIGFzIG1hcmtkb3duIGZyb20gXCIuL3NyYy9tYXJrZG93bi50c1wiO1xuZXhwb3J0ICogYXMgb3V0Ym94IGZyb20gXCIuL3NyYy9vdXRib3gudHNcIjtcbmV4cG9ydCAqIGFzIHBsdWdpbiBmcm9tIFwiLi9zcmMvcGx1Z2luLnRzXCI7XG5leHBvcnQgKiBhcyBzZW5kZXIgZnJvbSBcIi4vc3JjL3NlbmRlci50c1wiO1xuZXhwb3J0ICogYXMgdG9vbCBmcm9tIFwiLi9zcmMvdG9vbC50c1wiO1xuZXhwb3J0ICogYXMgdHJhbnNwb3J0IGZyb20gXCIuL3NyYy90cmFuc3BvcnQudHNcIjtcbmV4cG9ydCB7IFNFU1NJT05fQ09PS0lFLCBBdXRoR2F0ZSB9IGZyb20gXCIuL3NyYy9hdXRoL2dhdGUudHNcIjtcbmV4cG9ydCB0eXBlIHsgQXV0aEdhdGVPcHRpb25zIH0gZnJvbSBcIi4vc3JjL2F1dGgvZ2F0ZS50c1wiO1xuZXhwb3J0IHsgQ29kZVN0b3JlIH0gZnJvbSBcIi4vc3JjL2F1dGgvb3RwLnRzXCI7XG5leHBvcnQgdHlwZSB7IFZlcmlmeU91dGNvbWUgfSBmcm9tIFwiLi9zcmMvYXV0aC9vdHAudHNcIjtcbmV4cG9ydCB7IFJhdGVMaW1pdGVyIH0gZnJvbSBcIi4vc3JjL2F1dGgvcmF0ZS1saW1pdC50c1wiO1xuZXhwb3J0IHsgZGVmYXVsdEVtYWlsQnJhbmQgfSBmcm9tIFwiLi9zcmMvYnJhbmQudHNcIjtcbmV4cG9ydCB0eXBlIHsgRW1haWxCcmFuZCB9IGZyb20gXCIuL3NyYy9icmFuZC50c1wiO1xuZXhwb3J0IHsgREVGQVVMVF9TTVRQX1BPUlQsIElNUExJQ0lUX1RMU19TTVRQX1BPUlQsIEVNQUlMX0NPTkZJR19TQ0hFTUEgfSBmcm9tIFwiLi9zcmMvY29uZmlnLnRzXCI7XG5leHBvcnQgdHlwZSB7IFNlbmRlclBvbGljeSwgU210cENvbmZpZywgRW1haWxQbHVnaW5Db25maWcsIEF1dGhDb25maWcsIFJlc29sdmVkU210cENvbmZpZywgUmVzb2x2ZWRGaWxlQ29uZmlnLCBSZXNvbHZlZEVtYWlsQ29uZmlnLCBSZXNvbHZlZEF1dGhDb25maWcgfSBmcm9tIFwiLi9zcmMvY29uZmlnLnRzXCI7XG5leHBvcnQgeyBTRU5EX1RJTUVPVVRfTVMsIFZFUklGWV9USU1FT1VUX01TLCBWRVJJRllfQVRURU1QVFMsIE1BWF9BVFRBQ0hNRU5UX0JZVEVTLCBNQVhfQVRUQUNITUVOVFNfVE9UQUxfQllURVMsIE1BWF9BVFRBQ0hNRU5UX0NPVU5ULCBNQVhfQk9EWV9DSEFSUywgRU1BSUxfU0VORF9TRVRUSU5HUywgRU1BSUxfVkVSSUZZX1NFVFRJTkdTLCBFTUFJTF9TRU5ERVJTX1NFVFRJTkdTIH0gZnJvbSBcIi4vc3JjL2RlZmF1bHRzLnRzXCI7XG5leHBvcnQgdHlwZSB7IEVtYWlsRXhlY3V0ZUNvbmZpZywgRW1haWxFeGVjdXRpb25TZXR0aW5ncyB9IGZyb20gXCIuL3NyYy9kZWZhdWx0cy50c1wiO1xuZXhwb3J0IHsgZXNjYXBlSHRtbCB9IGZyb20gXCIuL3NyYy9lbWFpbC1odG1sLnRzXCI7XG5leHBvcnQgdHlwZSB7IEVtYWlsSHRtbE9wdGlvbnMgfSBmcm9tIFwiLi9zcmMvZW1haWwtaHRtbC50c1wiO1xuZXhwb3J0IHsgRW1haWxQbHVnaW4sIGVtYWlsIH0gZnJvbSBcIi4vc3JjL3BsdWdpbi50c1wiO1xuZXhwb3J0IHsgU0VORF9FTUFJTF9ERVNDUklQVElPTiB9IGZyb20gXCIuL3NyYy90b29sLnRzXCI7XG5leHBvcnQgdHlwZSB7IEVtYWlsVG9vbE9wdGlvbnMgfSBmcm9tIFwiLi9zcmMvdG9vbC50c1wiO1xuZXhwb3J0IHR5cGUgeyBFbWFpbEV4ZWN1dG9yLCBFbWFpbFJ1bnRpbWUgfSBmcm9tIFwiLi9zcmMvdHJhbnNwb3J0LnRzXCI7XG4iXX0=
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Unified access allow-list matching for the email-OTP gate.
3
+ *
4
+ * Each pattern in the configured list is one of three shapes, tried in order:
5
+ *
6
+ * - **domain shortcut** - `databricks.com` or `@databricks.com`: matches any
7
+ * address whose domain equals it (case-insensitive). The leading `@` is
8
+ * optional and stripped.
9
+ * - **glob** - contains `*` or `?`, e.g. `*.databricks.com` or
10
+ * `*@databricks.com`: matched against the WHOLE address with shell-style
11
+ * wildcards (`*` = any run, `?` = one char).
12
+ * - **regex** - wrapped in slashes, `/.../ [flags]`: compiled and tested
13
+ * against the whole address. An invalid regex never matches (it is skipped
14
+ * with a warning rather than throwing).
15
+ *
16
+ * An EMPTY list matches nobody (fail closed): an app that enables the gate but
17
+ * configures no patterns lets no one in, which is the safe default.
18
+ *
19
+ * @module
20
+ */
21
+ /**
22
+ * True when `email` is allowed by ANY pattern in `patterns`. An empty (or
23
+ * missing) list allows nobody - the gate fails closed.
24
+ */
25
+ export declare function matchesAllowlist(email: string, patterns: readonly string[] | undefined): boolean;
26
+ /** Rough shape check so a clearly-invalid address is rejected before any work. */
27
+ export declare function looksLikeEmail(value: string): boolean;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Unified access allow-list matching for the email-OTP gate.
3
+ *
4
+ * Each pattern in the configured list is one of three shapes, tried in order:
5
+ *
6
+ * - **domain shortcut** - `databricks.com` or `@databricks.com`: matches any
7
+ * address whose domain equals it (case-insensitive). The leading `@` is
8
+ * optional and stripped.
9
+ * - **glob** - contains `*` or `?`, e.g. `*.databricks.com` or
10
+ * `*@databricks.com`: matched against the WHOLE address with shell-style
11
+ * wildcards (`*` = any run, `?` = one char).
12
+ * - **regex** - wrapped in slashes, `/.../ [flags]`: compiled and tested
13
+ * against the whole address. An invalid regex never matches (it is skipped
14
+ * with a warning rather than throwing).
15
+ *
16
+ * An EMPTY list matches nobody (fail closed): an app that enables the gate but
17
+ * configures no patterns lets no one in, which is the safe default.
18
+ *
19
+ * @module
20
+ */
21
+ import { log } from "@dbx-tools/shared-core";
22
+ const logger = log.logger("email:auth:allowlist");
23
+ /** Escape a string for literal use inside a `RegExp`. */
24
+ function escapeRegExp(value) {
25
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
+ }
27
+ /** Compile a shell-style glob (`*`, `?`) into an anchored, case-insensitive RegExp. */
28
+ function globToRegExp(glob) {
29
+ const body = glob
30
+ .split(/([*?])/)
31
+ .map((part) => (part === "*" ? ".*" : part === "?" ? "." : escapeRegExp(part)))
32
+ .join("");
33
+ return new RegExp(`^${body}$`, "i");
34
+ }
35
+ /** Parse a `/pattern/flags` string into a RegExp, or `undefined` if malformed. */
36
+ function parseRegexLiteral(pattern) {
37
+ const match = /^\/(.+)\/([a-z]*)$/is.exec(pattern);
38
+ if (!match)
39
+ return undefined;
40
+ try {
41
+ const flags = match[2].includes("i") ? match[2] : `${match[2]}i`;
42
+ return new RegExp(match[1], flags);
43
+ }
44
+ catch (error) {
45
+ logger.warn("ignoring invalid regex allow-list pattern", { pattern, error });
46
+ return undefined;
47
+ }
48
+ }
49
+ /** True when `email` matches a single allow-list `pattern`. */
50
+ function matchesPattern(email, pattern) {
51
+ const trimmed = pattern.trim();
52
+ if (!trimmed)
53
+ return false;
54
+ const address = email.trim().toLowerCase();
55
+ // Regex literal: /.../
56
+ if (trimmed.startsWith("/")) {
57
+ const re = parseRegexLiteral(trimmed);
58
+ return re ? re.test(address) : false;
59
+ }
60
+ // Glob: contains a wildcard.
61
+ if (trimmed.includes("*") || trimmed.includes("?")) {
62
+ return globToRegExp(trimmed).test(address);
63
+ }
64
+ // Domain shortcut: `@d.com` or `d.com` -> match the address's domain.
65
+ const domain = trimmed.replace(/^@/, "").toLowerCase();
66
+ const at = address.lastIndexOf("@");
67
+ return at >= 0 && address.slice(at + 1) === domain;
68
+ }
69
+ /**
70
+ * True when `email` is allowed by ANY pattern in `patterns`. An empty (or
71
+ * missing) list allows nobody - the gate fails closed.
72
+ */
73
+ export function matchesAllowlist(email, patterns) {
74
+ if (!email || !patterns || patterns.length === 0)
75
+ return false;
76
+ return patterns.some((pattern) => matchesPattern(email, pattern));
77
+ }
78
+ /** Rough shape check so a clearly-invalid address is rejected before any work. */
79
+ export function looksLikeEmail(value) {
80
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
81
+ }
82
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWxsb3dsaXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2F1dGgvYWxsb3dsaXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBbUJHO0FBRUgsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRTdDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsc0JBQXNCLENBQUMsQ0FBQztBQUVsRCx5REFBeUQ7QUFDekQsU0FBUyxZQUFZLENBQUMsS0FBYTtJQUNqQyxPQUFPLEtBQUssQ0FBQyxPQUFPLENBQUMscUJBQXFCLEVBQUUsTUFBTSxDQUFDLENBQUM7QUFDdEQsQ0FBQztBQUVELHVGQUF1RjtBQUN2RixTQUFTLFlBQVksQ0FBQyxJQUFZO0lBQ2hDLE1BQU0sSUFBSSxHQUFHLElBQUk7U0FDZCxLQUFLLENBQUMsUUFBUSxDQUFDO1NBQ2YsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDLElBQUksS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztTQUM5RSxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDWixPQUFPLElBQUksTUFBTSxDQUFDLElBQUksSUFBSSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUM7QUFDdEMsQ0FBQztBQUVELGtGQUFrRjtBQUNsRixTQUFTLGlCQUFpQixDQUFDLE9BQWU7SUFDeEMsTUFBTSxLQUFLLEdBQUcsc0JBQXNCLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ25ELElBQUksQ0FBQyxLQUFLO1FBQUUsT0FBTyxTQUFTLENBQUM7SUFDN0IsSUFBSSxDQUFDO1FBQ0gsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBRSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUUsR0FBRyxDQUFDO1FBQ3BFLE9BQU8sSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBRSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ3RDLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsTUFBTSxDQUFDLElBQUksQ0FBQywyQ0FBMkMsRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO1FBQzdFLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7QUFDSCxDQUFDO0FBRUQsK0RBQStEO0FBQy9ELFNBQVMsY0FBYyxDQUFDLEtBQWEsRUFBRSxPQUFlO0lBQ3BELE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUMvQixJQUFJLENBQUMsT0FBTztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQzNCLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUUzQyx1QkFBdUI7SUFDdkIsSUFBSSxPQUFPLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDNUIsTUFBTSxFQUFFLEdBQUcsaUJBQWlCLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDdEMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztJQUN2QyxDQUFDO0lBRUQsNkJBQTZCO0lBQzdCLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDbkQsT0FBTyxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzdDLENBQUM7SUFFRCxzRUFBc0U7SUFDdEUsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDdkQsTUFBTSxFQUFFLEdBQUcsT0FBTyxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNwQyxPQUFPLEVBQUUsSUFBSSxDQUFDLElBQUksT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDLEtBQUssTUFBTSxDQUFDO0FBQ3JELENBQUM7QUFFRDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsZ0JBQWdCLENBQUMsS0FBYSxFQUFFLFFBQXVDO0lBQ3JGLElBQUksQ0FBQyxLQUFLLElBQUksQ0FBQyxRQUFRLElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDL0QsT0FBTyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxjQUFjLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUM7QUFDcEUsQ0FBQztBQUVELGtGQUFrRjtBQUNsRixNQUFNLFVBQVUsY0FBYyxDQUFDLEtBQWE7SUFDMUMsT0FBTyw0QkFBNEIsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7QUFDekQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVW5pZmllZCBhY2Nlc3MgYWxsb3ctbGlzdCBtYXRjaGluZyBmb3IgdGhlIGVtYWlsLU9UUCBnYXRlLlxuICpcbiAqIEVhY2ggcGF0dGVybiBpbiB0aGUgY29uZmlndXJlZCBsaXN0IGlzIG9uZSBvZiB0aHJlZSBzaGFwZXMsIHRyaWVkIGluIG9yZGVyOlxuICpcbiAqICAgLSAqKmRvbWFpbiBzaG9ydGN1dCoqIC0gYGRhdGFicmlja3MuY29tYCBvciBgQGRhdGFicmlja3MuY29tYDogbWF0Y2hlcyBhbnlcbiAqICAgICBhZGRyZXNzIHdob3NlIGRvbWFpbiBlcXVhbHMgaXQgKGNhc2UtaW5zZW5zaXRpdmUpLiBUaGUgbGVhZGluZyBgQGAgaXNcbiAqICAgICBvcHRpb25hbCBhbmQgc3RyaXBwZWQuXG4gKiAgIC0gKipnbG9iKiogLSBjb250YWlucyBgKmAgb3IgYD9gLCBlLmcuIGAqLmRhdGFicmlja3MuY29tYCBvclxuICogICAgIGAqQGRhdGFicmlja3MuY29tYDogbWF0Y2hlZCBhZ2FpbnN0IHRoZSBXSE9MRSBhZGRyZXNzIHdpdGggc2hlbGwtc3R5bGVcbiAqICAgICB3aWxkY2FyZHMgKGAqYCA9IGFueSBydW4sIGA/YCA9IG9uZSBjaGFyKS5cbiAqICAgLSAqKnJlZ2V4KiogLSB3cmFwcGVkIGluIHNsYXNoZXMsIGAvLi4uLyBbZmxhZ3NdYDogY29tcGlsZWQgYW5kIHRlc3RlZFxuICogICAgIGFnYWluc3QgdGhlIHdob2xlIGFkZHJlc3MuIEFuIGludmFsaWQgcmVnZXggbmV2ZXIgbWF0Y2hlcyAoaXQgaXMgc2tpcHBlZFxuICogICAgIHdpdGggYSB3YXJuaW5nIHJhdGhlciB0aGFuIHRocm93aW5nKS5cbiAqXG4gKiBBbiBFTVBUWSBsaXN0IG1hdGNoZXMgbm9ib2R5IChmYWlsIGNsb3NlZCk6IGFuIGFwcCB0aGF0IGVuYWJsZXMgdGhlIGdhdGUgYnV0XG4gKiBjb25maWd1cmVzIG5vIHBhdHRlcm5zIGxldHMgbm8gb25lIGluLCB3aGljaCBpcyB0aGUgc2FmZSBkZWZhdWx0LlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwiZW1haWw6YXV0aDphbGxvd2xpc3RcIik7XG5cbi8qKiBFc2NhcGUgYSBzdHJpbmcgZm9yIGxpdGVyYWwgdXNlIGluc2lkZSBhIGBSZWdFeHBgLiAqL1xuZnVuY3Rpb24gZXNjYXBlUmVnRXhwKHZhbHVlOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gdmFsdWUucmVwbGFjZSgvWy4qKz9eJHt9KCl8W1xcXVxcXFxdL2csIFwiXFxcXCQmXCIpO1xufVxuXG4vKiogQ29tcGlsZSBhIHNoZWxsLXN0eWxlIGdsb2IgKGAqYCwgYD9gKSBpbnRvIGFuIGFuY2hvcmVkLCBjYXNlLWluc2Vuc2l0aXZlIFJlZ0V4cC4gKi9cbmZ1bmN0aW9uIGdsb2JUb1JlZ0V4cChnbG9iOiBzdHJpbmcpOiBSZWdFeHAge1xuICBjb25zdCBib2R5ID0gZ2xvYlxuICAgIC5zcGxpdCgvKFsqP10pLylcbiAgICAubWFwKChwYXJ0KSA9PiAocGFydCA9PT0gXCIqXCIgPyBcIi4qXCIgOiBwYXJ0ID09PSBcIj9cIiA/IFwiLlwiIDogZXNjYXBlUmVnRXhwKHBhcnQpKSlcbiAgICAuam9pbihcIlwiKTtcbiAgcmV0dXJuIG5ldyBSZWdFeHAoYF4ke2JvZHl9JGAsIFwiaVwiKTtcbn1cblxuLyoqIFBhcnNlIGEgYC9wYXR0ZXJuL2ZsYWdzYCBzdHJpbmcgaW50byBhIFJlZ0V4cCwgb3IgYHVuZGVmaW5lZGAgaWYgbWFsZm9ybWVkLiAqL1xuZnVuY3Rpb24gcGFyc2VSZWdleExpdGVyYWwocGF0dGVybjogc3RyaW5nKTogUmVnRXhwIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgbWF0Y2ggPSAvXlxcLyguKylcXC8oW2Etel0qKSQvaXMuZXhlYyhwYXR0ZXJuKTtcbiAgaWYgKCFtYXRjaCkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgdHJ5IHtcbiAgICBjb25zdCBmbGFncyA9IG1hdGNoWzJdIS5pbmNsdWRlcyhcImlcIikgPyBtYXRjaFsyXSEgOiBgJHttYXRjaFsyXSF9aWA7XG4gICAgcmV0dXJuIG5ldyBSZWdFeHAobWF0Y2hbMV0hLCBmbGFncyk7XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgbG9nZ2VyLndhcm4oXCJpZ25vcmluZyBpbnZhbGlkIHJlZ2V4IGFsbG93LWxpc3QgcGF0dGVyblwiLCB7IHBhdHRlcm4sIGVycm9yIH0pO1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cblxuLyoqIFRydWUgd2hlbiBgZW1haWxgIG1hdGNoZXMgYSBzaW5nbGUgYWxsb3ctbGlzdCBgcGF0dGVybmAuICovXG5mdW5jdGlvbiBtYXRjaGVzUGF0dGVybihlbWFpbDogc3RyaW5nLCBwYXR0ZXJuOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgY29uc3QgdHJpbW1lZCA9IHBhdHRlcm4udHJpbSgpO1xuICBpZiAoIXRyaW1tZWQpIHJldHVybiBmYWxzZTtcbiAgY29uc3QgYWRkcmVzcyA9IGVtYWlsLnRyaW0oKS50b0xvd2VyQ2FzZSgpO1xuXG4gIC8vIFJlZ2V4IGxpdGVyYWw6IC8uLi4vXG4gIGlmICh0cmltbWVkLnN0YXJ0c1dpdGgoXCIvXCIpKSB7XG4gICAgY29uc3QgcmUgPSBwYXJzZVJlZ2V4TGl0ZXJhbCh0cmltbWVkKTtcbiAgICByZXR1cm4gcmUgPyByZS50ZXN0KGFkZHJlc3MpIDogZmFsc2U7XG4gIH1cblxuICAvLyBHbG9iOiBjb250YWlucyBhIHdpbGRjYXJkLlxuICBpZiAodHJpbW1lZC5pbmNsdWRlcyhcIipcIikgfHwgdHJpbW1lZC5pbmNsdWRlcyhcIj9cIikpIHtcbiAgICByZXR1cm4gZ2xvYlRvUmVnRXhwKHRyaW1tZWQpLnRlc3QoYWRkcmVzcyk7XG4gIH1cblxuICAvLyBEb21haW4gc2hvcnRjdXQ6IGBAZC5jb21gIG9yIGBkLmNvbWAgLT4gbWF0Y2ggdGhlIGFkZHJlc3MncyBkb21haW4uXG4gIGNvbnN0IGRvbWFpbiA9IHRyaW1tZWQucmVwbGFjZSgvXkAvLCBcIlwiKS50b0xvd2VyQ2FzZSgpO1xuICBjb25zdCBhdCA9IGFkZHJlc3MubGFzdEluZGV4T2YoXCJAXCIpO1xuICByZXR1cm4gYXQgPj0gMCAmJiBhZGRyZXNzLnNsaWNlKGF0ICsgMSkgPT09IGRvbWFpbjtcbn1cblxuLyoqXG4gKiBUcnVlIHdoZW4gYGVtYWlsYCBpcyBhbGxvd2VkIGJ5IEFOWSBwYXR0ZXJuIGluIGBwYXR0ZXJuc2AuIEFuIGVtcHR5IChvclxuICogbWlzc2luZykgbGlzdCBhbGxvd3Mgbm9ib2R5IC0gdGhlIGdhdGUgZmFpbHMgY2xvc2VkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gbWF0Y2hlc0FsbG93bGlzdChlbWFpbDogc3RyaW5nLCBwYXR0ZXJuczogcmVhZG9ubHkgc3RyaW5nW10gfCB1bmRlZmluZWQpOiBib29sZWFuIHtcbiAgaWYgKCFlbWFpbCB8fCAhcGF0dGVybnMgfHwgcGF0dGVybnMubGVuZ3RoID09PSAwKSByZXR1cm4gZmFsc2U7XG4gIHJldHVybiBwYXR0ZXJucy5zb21lKChwYXR0ZXJuKSA9PiBtYXRjaGVzUGF0dGVybihlbWFpbCwgcGF0dGVybikpO1xufVxuXG4vKiogUm91Z2ggc2hhcGUgY2hlY2sgc28gYSBjbGVhcmx5LWludmFsaWQgYWRkcmVzcyBpcyByZWplY3RlZCBiZWZvcmUgYW55IHdvcmsuICovXG5leHBvcnQgZnVuY3Rpb24gbG9va3NMaWtlRW1haWwodmFsdWU6IHN0cmluZyk6IGJvb2xlYW4ge1xuICByZXR1cm4gL15bXlxcc0BdK0BbXlxcc0BdK1xcLlteXFxzQF0rJC8udGVzdCh2YWx1ZS50cmltKCkpO1xufVxuIl19
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The email-OTP access gate: runtime + Express middleware + request handlers.
3
+ *
4
+ * {@link AuthGate} owns the allow-list, rate limiters, code store, and session
5
+ * signing. The plugin wires four routes to it (`request`/`verify`/`logout`/
6
+ * `status`) and mounts {@link AuthGate.middleware} so every OTHER request needs a
7
+ * valid session cookie.
8
+ *
9
+ * Design points:
10
+ * - `request` ALWAYS resolves `{ ok: true }` (anti-enumeration): a code is only
11
+ * generated + emailed when the address is allow-listed AND under the rate
12
+ * limit, but the caller can't tell the difference.
13
+ * - the session lives in an HttpOnly + SameSite=Lax cookie (Secure in prod), so
14
+ * it survives reloads and is not readable by page scripts.
15
+ * - fail-open on a missing signing secret (see `otp.ts`): a Databricks App is
16
+ * already access-limited; an unset `AUTH_JWT_SECRET` degrades to
17
+ * non-durable sessions, not a lockout.
18
+ *
19
+ * @module
20
+ */
21
+ import type { CookieOptions, NextFunction, Request, Response } from "express";
22
+ import type { AuthStatus } from "@dbx-tools/shared-email";
23
+ /** Cookie the session JWT rides in. */
24
+ export declare const SESSION_COOKIE = "dbx_auth";
25
+ /** Resolved gate configuration. */
26
+ export interface AuthGateOptions {
27
+ /** Allow-list patterns (domain / glob / `/regex/`). Empty = allow nobody. */
28
+ readonly allow: readonly string[];
29
+ /** Session lifetime in seconds. */
30
+ readonly sessionTtlSeconds: number;
31
+ /** One-time code lifetime in seconds. */
32
+ readonly codeTtlSeconds: number;
33
+ /** Max verify attempts per issued code. */
34
+ readonly maxAttempts: number;
35
+ /** Send the code email. Returns nothing; failures are logged, not surfaced. */
36
+ readonly sendCode: (email: string, code: string) => Promise<void>;
37
+ /** True in production (sets the `Secure` cookie flag). */
38
+ readonly secureCookies: boolean;
39
+ }
40
+ /** The email-OTP gate for one app. */
41
+ export declare class AuthGate {
42
+ private readonly options;
43
+ private readonly codes;
44
+ private readonly requestLimiter;
45
+ private readonly verifyLimiter;
46
+ constructor(options: AuthGateOptions);
47
+ /**
48
+ * Handle `POST /auth/request`. Always resolves `{ ok: true }` (plus a
49
+ * `retryAfter` when rate-limited); a code is issued + emailed only for an
50
+ * allow-listed address under the limit. Errors in sending are swallowed so the
51
+ * response never reveals whether an address exists / is allowed.
52
+ */
53
+ handleRequest(email: string, ip: string): Promise<{
54
+ ok: true;
55
+ retryAfter?: number;
56
+ }>;
57
+ /**
58
+ * Handle `POST /auth/verify`. On a correct code, returns the session `token`
59
+ * plus the `cookieOptions` for it so the route sets it with Express's own
60
+ * `res.cookie(SESSION_COOKIE, token, options)` (no hand-rolled Set-Cookie).
61
+ * Failures return `{ ok: false }` with a generic reason.
62
+ */
63
+ handleVerify(email: string, code: string, ip: string, secure: boolean): Promise<{
64
+ ok: boolean;
65
+ token?: string;
66
+ cookieOptions?: CookieOptions;
67
+ retryAfter?: number;
68
+ }>;
69
+ /** Express cookie options for the session (used with `res.cookie`/`res.clearCookie`). */
70
+ cookieOptions(secure: boolean): CookieOptions;
71
+ /** Resolve the authenticated email for a request, or `undefined`. */
72
+ authenticate(req: Request): Promise<string | undefined>;
73
+ /** The `GET /auth/status` payload for a request. */
74
+ status(req: Request): Promise<AuthStatus>;
75
+ /**
76
+ * Express middleware gating requests on the APP router (mounted at the app
77
+ * root). Requests to the login flow (`<emailBase>/auth/*`) pass through so a
78
+ * caller can authenticate; the app's own API/asset requests need a valid
79
+ * session or get a 401. `emailBase` is the plugin's mount path (e.g.
80
+ * `/api/email`) so the open prefix is matched against the full request path.
81
+ */
82
+ middleware(emailBase: string): (req: Request, res: Response, next: NextFunction) => void;
83
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The email-OTP access gate: runtime + Express middleware + request handlers.
3
+ *
4
+ * {@link AuthGate} owns the allow-list, rate limiters, code store, and session
5
+ * signing. The plugin wires four routes to it (`request`/`verify`/`logout`/
6
+ * `status`) and mounts {@link AuthGate.middleware} so every OTHER request needs a
7
+ * valid session cookie.
8
+ *
9
+ * Design points:
10
+ * - `request` ALWAYS resolves `{ ok: true }` (anti-enumeration): a code is only
11
+ * generated + emailed when the address is allow-listed AND under the rate
12
+ * limit, but the caller can't tell the difference.
13
+ * - the session lives in an HttpOnly + SameSite=Lax cookie (Secure in prod), so
14
+ * it survives reloads and is not readable by page scripts.
15
+ * - fail-open on a missing signing secret (see `otp.ts`): a Databricks App is
16
+ * already access-limited; an unset `AUTH_JWT_SECRET` degrades to
17
+ * non-durable sessions, not a lockout.
18
+ *
19
+ * @module
20
+ */
21
+ import { http, log } from "@dbx-tools/shared-core";
22
+ import { looksLikeEmail, matchesAllowlist } from "./allowlist.js";
23
+ import { CodeStore, signSession, verifySession } from "./otp.js";
24
+ import { RateLimiter } from "./rate-limit.js";
25
+ const logger = log.logger("email:auth");
26
+ /** Cookie the session JWT rides in. */
27
+ export const SESSION_COOKIE = "dbx_auth";
28
+ /** Route prefix (under the plugin base `/api/email`) the gate leaves open. */
29
+ const AUTH_PATH_PREFIX = "/auth";
30
+ /** The email-OTP gate for one app. */
31
+ export class AuthGate {
32
+ options;
33
+ codes;
34
+ // Separate limiters: requesting a code is cheap-to-abuse (email spam), verifying
35
+ // is a brute-force surface. Per-email AND per-IP so neither axis alone is a bypass.
36
+ requestLimiter = new RateLimiter(5, 15 * 60 * 1000);
37
+ verifyLimiter = new RateLimiter(10, 15 * 60 * 1000);
38
+ constructor(options) {
39
+ this.options = options;
40
+ this.codes = new CodeStore(options.codeTtlSeconds * 1000, options.maxAttempts);
41
+ }
42
+ /**
43
+ * Handle `POST /auth/request`. Always resolves `{ ok: true }` (plus a
44
+ * `retryAfter` when rate-limited); a code is issued + emailed only for an
45
+ * allow-listed address under the limit. Errors in sending are swallowed so the
46
+ * response never reveals whether an address exists / is allowed.
47
+ */
48
+ async handleRequest(email, ip) {
49
+ const address = email.trim().toLowerCase();
50
+ const byIp = this.requestLimiter.hit(`ip:${ip}`);
51
+ const byEmail = this.requestLimiter.hit(`email:${address}`);
52
+ if (!byIp.allowed || !byEmail.allowed) {
53
+ return { ok: true, retryAfter: byIp.retryAfter ?? byEmail.retryAfter };
54
+ }
55
+ if (looksLikeEmail(address) && matchesAllowlist(address, this.options.allow)) {
56
+ const code = this.codes.issue(address);
57
+ try {
58
+ await this.options.sendCode(address, code);
59
+ }
60
+ catch (error) {
61
+ logger.warn("failed to send OTP email", { error });
62
+ }
63
+ }
64
+ return { ok: true };
65
+ }
66
+ /**
67
+ * Handle `POST /auth/verify`. On a correct code, returns the session `token`
68
+ * plus the `cookieOptions` for it so the route sets it with Express's own
69
+ * `res.cookie(SESSION_COOKIE, token, options)` (no hand-rolled Set-Cookie).
70
+ * Failures return `{ ok: false }` with a generic reason.
71
+ */
72
+ async handleVerify(email, code, ip, secure) {
73
+ const address = email.trim().toLowerCase();
74
+ const byIp = this.verifyLimiter.hit(`ip:${ip}`);
75
+ const byEmail = this.verifyLimiter.hit(`email:${address}`);
76
+ if (!byIp.allowed || !byEmail.allowed) {
77
+ return { ok: false, retryAfter: byIp.retryAfter ?? byEmail.retryAfter };
78
+ }
79
+ if (this.codes.verify(address, code.trim()) !== "ok")
80
+ return { ok: false };
81
+ // Correct code: clear the caller's request/verify budget and mint a session.
82
+ this.requestLimiter.reset(`email:${address}`);
83
+ this.verifyLimiter.reset(`email:${address}`);
84
+ const token = await signSession(address, this.options.sessionTtlSeconds);
85
+ return { ok: true, token, cookieOptions: this.cookieOptions(secure) };
86
+ }
87
+ /** Express cookie options for the session (used with `res.cookie`/`res.clearCookie`). */
88
+ cookieOptions(secure) {
89
+ return {
90
+ httpOnly: true,
91
+ sameSite: "lax",
92
+ secure: secure || this.options.secureCookies,
93
+ path: "/",
94
+ maxAge: this.options.sessionTtlSeconds * 1000,
95
+ };
96
+ }
97
+ /** Resolve the authenticated email for a request, or `undefined`. */
98
+ async authenticate(req) {
99
+ // Reuse shared-core's cookie parser (accepts an Express req directly) rather
100
+ // than re-implementing header splitting here.
101
+ const token = http.parseCookies(req)[SESSION_COOKIE];
102
+ return verifySession(token);
103
+ }
104
+ /** The `GET /auth/status` payload for a request. */
105
+ async status(req) {
106
+ const email = await this.authenticate(req);
107
+ return { authenticated: Boolean(email), email, enabled: true };
108
+ }
109
+ /**
110
+ * Express middleware gating requests on the APP router (mounted at the app
111
+ * root). Requests to the login flow (`<emailBase>/auth/*`) pass through so a
112
+ * caller can authenticate; the app's own API/asset requests need a valid
113
+ * session or get a 401. `emailBase` is the plugin's mount path (e.g.
114
+ * `/api/email`) so the open prefix is matched against the full request path.
115
+ */
116
+ middleware(emailBase) {
117
+ const openPrefix = `${emailBase}${AUTH_PATH_PREFIX}`;
118
+ return (req, res, next) => {
119
+ if (req.path.startsWith(openPrefix)) {
120
+ next();
121
+ return;
122
+ }
123
+ void this.authenticate(req).then((email) => {
124
+ if (email) {
125
+ next();
126
+ }
127
+ else {
128
+ res.status(401).json({ error: "authentication required", loginPath: openPrefix });
129
+ }
130
+ });
131
+ };
132
+ }
133
+ }
134
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2F0ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9hdXRoL2dhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFHSCxPQUFPLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRW5ELE9BQU8sRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNsRSxPQUFPLEVBQUUsU0FBUyxFQUFFLFdBQVcsRUFBRSxhQUFhLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFDakUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRTlDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7QUFFeEMsdUNBQXVDO0FBQ3ZDLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxVQUFVLENBQUM7QUFFekMsOEVBQThFO0FBQzlFLE1BQU0sZ0JBQWdCLEdBQUcsT0FBTyxDQUFDO0FBa0JqQyxzQ0FBc0M7QUFDdEMsTUFBTSxPQUFPLFFBQVE7SUFPVTtJQU5aLEtBQUssQ0FBWTtJQUNsQyxpRkFBaUY7SUFDakYsb0ZBQW9GO0lBQ25FLGNBQWMsR0FBRyxJQUFJLFdBQVcsQ0FBQyxDQUFDLEVBQUUsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztJQUNwRCxhQUFhLEdBQUcsSUFBSSxXQUFXLENBQUMsRUFBRSxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUM7SUFFckUsWUFBNkIsT0FBd0I7UUFBeEIsWUFBTyxHQUFQLE9BQU8sQ0FBaUI7UUFDbkQsSUFBSSxDQUFDLEtBQUssR0FBRyxJQUFJLFNBQVMsQ0FBQyxPQUFPLENBQUMsY0FBYyxHQUFHLElBQUksRUFBRSxPQUFPLENBQUMsV0FBVyxDQUFDLENBQUM7SUFDakYsQ0FBQztJQUVEOzs7OztP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QUUsQ0FBQztvQkFDVixJQUFJLEVBQUUsQ0FBQztnQkFDVCxDQUFDO3FCQUFNLENBQUM7b0JBQ04sR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxLQUFLLEVBQUUseUJBQXlCLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUM7Z0JBQ3BGLENBQUM7WUFDSCxDQUFDLENBQUMsQ0FBQztRQUNMLENBQUMsQ0FBQztJQUNKLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVGhlIGVtYWlsLU9UUCBhY2Nlc3MgZ2F0ZTogcnVudGltZSArIEV4cHJlc3MgbWlkZGxld2FyZSArIHJlcXVlc3QgaGFuZGxlcnMuXG4gKlxuICoge0BsaW5rIEF1dGhHYXRlfSBvd25zIHRoZSBhbGxvdy1saXN0LCByYXRlIGxpbWl0ZXJzLCBjb2RlIHN0b3JlLCBhbmQgc2Vzc2lvblxuICogc2lnbmluZy4gVGhlIHBsdWdpbiB3aXJlcyBmb3VyIHJvdXRlcyB0byBpdCAoYHJlcXVlc3RgL2B2ZXJpZnlgL2Bsb2dvdXRgL1xuICogYHN0YXR1c2ApIGFuZCBtb3VudHMge0BsaW5rIEF1dGhHYXRlLm1pZGRsZXdhcmV9IHNvIGV2ZXJ5IE9USEVSIHJlcXVlc3QgbmVlZHMgYVxuICogdmFsaWQgc2Vzc2lvbiBjb29raWUuXG4gKlxuICogRGVzaWduIHBvaW50czpcbiAqICAgLSBgcmVxdWVzdGAgQUxXQVlTIHJlc29sdmVzIGB7IG9rOiB0cnVlIH1gIChhbnRpLWVudW1lcmF0aW9uKTogYSBjb2RlIGlzIG9ubHlcbiAqICAgICBnZW5lcmF0ZWQgKyBlbWFpbGVkIHdoZW4gdGhlIGFkZHJlc3MgaXMgYWxsb3ctbGlzdGVkIEFORCB1bmRlciB0aGUgcmF0ZVxuICogICAgIGxpbWl0LCBidXQgdGhlIGNhbGxlciBjYW4ndCB0ZWxsIHRoZSBkaWZmZXJlbmNlLlxuICogICAtIHRoZSBzZXNzaW9uIGxpdmVzIGluIGFuIEh0dHBPbmx5ICsgU2FtZVNpdGU9TGF4IGNvb2tpZSAoU2VjdXJlIGluIHByb2QpLCBzb1xuICogICAgIGl0IHN1cnZpdmVzIHJlbG9hZHMgYW5kIGlzIG5vdCByZWFkYWJsZSBieSBwYWdlIHNjcmlwdHMuXG4gKiAgIC0gZmFpbC1vcGVuIG9uIGEgbWlzc2luZyBzaWduaW5nIHNlY3JldCAoc2VlIGBvdHAudHNgKTogYSBEYXRhYnJpY2tzIEFwcCBpc1xuICogICAgIGFscmVhZHkgYWNjZXNzLWxpbWl0ZWQ7IGFuIHVuc2V0IGBBVVRIX0pXVF9TRUNSRVRgIGRlZ3JhZGVzIHRvXG4gKiAgICAgbm9uLWR1cmFibGUgc2Vzc2lvbnMsIG5vdCBhIGxvY2tvdXQuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB0eXBlIHsgQ29va2llT3B0aW9ucywgTmV4dEZ1bmN0aW9uLCBSZXF1ZXN0LCBSZXNwb25zZSB9IGZyb20gXCJleHByZXNzXCI7XG5pbXBvcnQgeyBodHRwLCBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHR5cGUgeyBBdXRoU3RhdHVzIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWVtYWlsXCI7XG5pbXBvcnQgeyBsb29rc0xpa2VFbWFpbCwgbWF0Y2hlc0FsbG93bGlzdCB9IGZyb20gXCIuL2FsbG93bGlzdC50c1wiO1xuaW1wb3J0IHsgQ29kZVN0b3JlLCBzaWduU2Vzc2lvbiwgdmVyaWZ5U2Vzc2lvbiB9IGZyb20gXCIuL290cC50c1wiO1xuaW1wb3J0IHsgUmF0ZUxpbWl0ZXIgfSBmcm9tIFwiLi9yYXRlLWxpbWl0LnRzXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJlbWFpbDphdXRoXCIpO1xuXG4vKiogQ29va2llIHRoZSBzZXNzaW9uIEpXVCByaWRlcyBpbi4gKi9cbmV4cG9ydCBjb25zdCBTRVNTSU9OX0NPT0tJRSA9IFwiZGJ4X2F1dGhcIjtcblxuLyoqIFJvdXRlIHByZWZpeCAodW5kZXIgdGhlIHBsdWdpbiBiYXNlIGAvYXBpL2VtYWlsYCkgdGhlIGdhdGUgbGVhdmVzIG9wZW4uICovXG5jb25zdCBBVVRIX1BBVEhfUFJFRklYID0gXCIvYXV0aFwiO1xuXG4vKiogUmVzb2x2ZWQgZ2F0ZSBjb25maWd1cmF0aW9uLiAqL1xuZXhwb3J0IGludGVyZmFjZSBBdXRoR2F0ZU9wdGlvbnMge1xuICAvKiogQWxsb3ctbGlzdCBwYXR0ZXJucyAoZG9tYWluIC8gZ2xvYiAvIGAvcmVnZXgvYCkuIEVtcHR5ID0gYWxsb3cgbm9ib2R5LiAqL1xuICByZWFkb25seSBhbGxvdzogcmVhZG9ubHkgc3RyaW5nW107XG4gIC8qKiBTZXNzaW9uIGxpZmV0aW1lIGluIHNlY29uZHMuICovXG4gIHJlYWRvbmx5IHNlc3Npb25UdGxTZWNvbmRzOiBudW1iZXI7XG4gIC8qKiBPbmUtdGltZSBjb2RlIGxpZmV0aW1lIGluIHNlY29uZHMuICovXG4gIHJlYWRvbmx5IGNvZGVUdGxTZWNvbmRzOiBudW1iZXI7XG4gIC8qKiBNYXggdmVyaWZ5IGF0dGVtcHRzIHBlciBpc3N1ZWQgY29kZS4gKi9cbiAgcmVhZG9ubHkgbWF4QXR0ZW1wdHM6IG51bWJlcjtcbiAgLyoqIFNlbmQgdGhlIGNvZGUgZW1haWwuIFJldHVybnMgbm90aGluZzsgZmFpbHVyZXMgYXJlIGxvZ2dlZCwgbm90IHN1cmZhY2VkLiAqL1xuICByZWFkb25seSBzZW5kQ29kZTogKGVtYWlsOiBzdHJpbmcsIGNvZGU6IHN0cmluZykgPT4gUHJvbWlzZTx2b2lkPjtcbiAgLyoqIFRydWUgaW4gcHJvZHVjdGlvbiAoc2V0cyB0aGUgYFNlY3VyZWAgY29va2llIGZsYWcpLiAqL1xuICByZWFkb25seSBzZWN1cmVDb29raWVzOiBib29sZWFuO1xufVxuXG4vKiogVGhlIGVtYWlsLU9UUCBnYXRlIGZvciBvbmUgYXBwLiAqL1xuZXhwb3J0IGNsYXNzIEF1dGhHYXRlIHtcbiAgcHJpdmF0ZSByZWFkb25seSBjb2RlczogQ29kZVN0b3JlO1xuICAvLyBTZXBhcmF0ZSBsaW1pdGVyczogcmVxdWVzdGluZyBhIGNvZGUgaXMgY2hlYXAtdG8tYWJ1c2UgKGVtYWlsIHNwYW0pLCB2ZXJpZnlpbmdcbiAgLy8gaXMgYSBicnV0ZS1mb3JjZSBzdXJmYWNlLiBQZXItZW1haWwgQU5EIHBlci1JUCBzbyBuZWl0aGVyIGF4aXMgYWxvbmUgaXMgYSBieXBhc3MuXG4gIHByaXZhdGUgcmVhZG9ubHkgcmVxdWVzdExpbWl0ZXIgPSBuZXcgUmF0ZUxpbWl0ZXIoNSwgMTUgKiA2MCAqIDEwMDApO1xuICBwcml2YXRlIHJlYWRvbmx5IHZlcmlmeUxpbWl0ZXIgPSBuZXcgUmF0ZUxpbWl0ZXIoMTAsIDE1ICogNjAgKiAxMDAwKTtcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHJlYWRvbmx5IG9wdGlvbnM6IEF1dGhHYXRlT3B0aW9ucykge1xuICAgIHRoaXMuY29kZXMgPSBuZXcgQ29kZVN0b3JlKG9wdGlvbnMuY29kZVR0bFNlY29uZHMgKiAxMDAwLCBvcHRpb25zLm1heEF0dGVtcHRzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBIYW5kbGUgYFBPU1QgL2F1dGgvcmVxdWVzdGAuIEFsd2F5cyByZXNvbHZlcyBgeyBvazogdHJ1ZSB9YCAocGx1cyBhXG4gICAqIGByZXRyeUFmdGVyYCB3aGVuIHJhdGUtbGltaXRlZCk7IGEgY29kZSBpcyBpc3N1ZWQgKyBlbWFpbGVkIG9ubHkgZm9yIGFuXG4gICAqIGFsbG93LWxpc3RlZCBhZGRyZXNzIHVuZGVyIHRoZSBsaW1pdC4gRXJyb3JzIGluIHNlbmRpbmcgYXJlIHN3YWxsb3dlZCBzbyB0aGVcbiAgICogcmVzcG9uc2UgbmV2ZXIgcmV2ZWFscyB3aGV0aGVyIGFuIGFkZHJlc3MgZXhpc3RzIC8gaXMgYWxsb3dlZC5cbiAgICovXG4gIGFzeW5jIGhhbmRsZVJlcXVlc3QoZW1haWw6IHN0cmluZywgaXA6IHN0cmluZyk6IFByb21pc2U8eyBvazogdHJ1ZTsgcmV0cnlBZnRlcj86IG51bWJlciB9PiB7XG4gICAgY29uc3QgYWRkcmVzcyA9IGVtYWlsLnRyaW0oKS50b0xvd2VyQ2FzZSgpO1xuICAgIGNvbnN0IGJ5SXAgPSB0aGlzLnJlcXVlc3RMaW1pdGVyLmhpdChgaXA6JHtpcH1gKTtcbiAgICBjb25zdCBieUVtYWlsID0gdGhpcy5yZXF1ZXN0TGltaXRlci5oaXQoYGVtYWlsOiR7YWRkcmVzc31gKTtcbiAgICBpZiAoIWJ5SXAuYWxsb3dlZCB8fCAhYnlFbWFpbC5hbGxvd2VkKSB7XG4gICAgICByZXR1cm4geyBvazogdHJ1ZSwgcmV0cnlBZnRlcjogYnlJcC5yZXRyeUFmdGVyID8/IGJ5RW1haWwucmV0cnlBZnRlciB9O1xuICAgIH1cbiAgICBpZiAobG9va3NMaWtlRW1haWwoYWRkcmVzcykgJiYgbWF0Y2hlc0FsbG93bGlzdChhZGRyZXNzLCB0aGlzLm9wdGlvbnMuYWxsb3cpKSB7XG4gICAgICBjb25zdCBjb2RlID0gdGhpcy5jb2Rlcy5pc3N1ZShhZGRyZXNzKTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHRoaXMub3B0aW9ucy5zZW5kQ29kZShhZGRyZXNzLCBjb2RlKTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKFwiZmFpbGVkIHRvIHNlbmQgT1RQIGVtYWlsXCIsIHsgZXJyb3IgfSk7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiB7IG9rOiB0cnVlIH07XG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlIGBQT1NUIC9hdXRoL3ZlcmlmeWAuIE9uIGEgY29ycmVjdCBjb2RlLCByZXR1cm5zIHRoZSBzZXNzaW9uIGB0b2tlbmBcbiAgICogcGx1cyB0aGUgYGNvb2tpZU9wdGlvbnNgIGZvciBpdCBzbyB0aGUgcm91dGUgc2V0cyBpdCB3aXRoIEV4cHJlc3MncyBvd25cbiAgICogYHJlcy5jb29raWUoU0VTU0lPTl9DT09LSUUsIHRva2VuLCBvcHRpb25zKWAgKG5vIGhhbmQtcm9sbGVkIFNldC1Db29raWUpLlxuICAgKiBGYWlsdXJlcyByZXR1cm4gYHsgb2s6IGZhbHNlIH1gIHdpdGggYSBnZW5lcmljIHJlYXNvbi5cbiAgICovXG4gIGFzeW5jIGhhbmRsZVZlcmlmeShcbiAgICBlbWFpbDogc3RyaW5nLFxuICAgIGNvZGU6IHN0cmluZyxcbiAgICBpcDogc3RyaW5nLFxuICAgIHNlY3VyZTogYm9vbGVhbixcbiAgKTogUHJvbWlzZTx7IG9rOiBib29sZWFuOyB0b2tlbj86IHN0cmluZzsgY29va2llT3B0aW9ucz86IENvb2tpZU9wdGlvbnM7IHJldHJ5QWZ0ZXI/OiBudW1iZXIgfT4ge1xuICAgIGNvbnN0IGFkZHJlc3MgPSBlbWFpbC50cmltKCkudG9Mb3dlckNhc2UoKTtcbiAgICBjb25zdCBieUlwID0gdGhpcy52ZXJpZnlMaW1pdGVyLmhpdChgaXA6JHtpcH1gKTtcbiAgICBjb25zdCBieUVtYWlsID0gdGhpcy52ZXJpZnlMaW1pdGVyLmhpdChgZW1haWw6JHthZGRyZXNzfWApO1xuICAgIGlmICghYnlJcC5hbGxvd2VkIHx8ICFieUVtYWlsLmFsbG93ZWQpIHtcbiAgICAgIHJldHVybiB7IG9rOiBmYWxzZSwgcmV0cnlBZnRlcjogYnlJcC5yZXRyeUFmdGVyID8/IGJ5RW1haWwucmV0cnlBZnRlciB9O1xuICAgIH1cbiAgICBpZiAodGhpcy5jb2Rlcy52ZXJpZnkoYWRkcmVzcywgY29kZS50cmltKCkpICE9PSBcIm9rXCIpIHJldHVybiB7IG9rOiBmYWxzZSB9O1xuICAgIC8vIENvcnJlY3QgY29kZTogY2xlYXIgdGhlIGNhbGxlcidzIHJlcXVlc3QvdmVyaWZ5IGJ1ZGdldCBhbmQgbWludCBhIHNlc3Npb24uXG4gICAgdGhpcy5yZXF1ZXN0TGltaXRlci5yZXNldChgZW1haWw6JHthZGRyZXNzfWApO1xuICAgIHRoaXMudmVyaWZ5TGltaXRlci5yZXNldChgZW1haWw6JHthZGRyZXNzfWApO1xuICAgIGNvbnN0IHRva2VuID0gYXdhaXQgc2lnblNlc3Npb24oYWRkcmVzcywgdGhpcy5vcHRpb25zLnNlc3Npb25UdGxTZWNvbmRzKTtcbiAgICByZXR1cm4geyBvazogdHJ1ZSwgdG9rZW4sIGNvb2tpZU9wdGlvbnM6IHRoaXMuY29va2llT3B0aW9ucyhzZWN1cmUpIH07XG4gIH1cblxuICAvKiogRXhwcmVzcyBjb29raWUgb3B0aW9ucyBmb3IgdGhlIHNlc3Npb24gKHVzZWQgd2l0aCBgcmVzLmNvb2tpZWAvYHJlcy5jbGVhckNvb2tpZWApLiAqL1xuICBjb29raWVPcHRpb25zKHNlY3VyZTogYm9vbGVhbik6IENvb2tpZU9wdGlvbnMge1xuICAgIHJldHVybiB7XG4gICAgICBodHRwT25seTogdHJ1ZSxcbiAgICAgIHNhbWVTaXRlOiBcImxheFwiLFxuICAgICAgc2VjdXJlOiBzZWN1cmUgfHwgdGhpcy5vcHRpb25zLnNlY3VyZUNvb2tpZXMsXG4gICAgICBwYXRoOiBcIi9cIixcbiAgICAgIG1heEFnZTogdGhpcy5vcHRpb25zLnNlc3Npb25UdGxTZWNvbmRzICogMTAwMCxcbiAgICB9O1xuICB9XG5cbiAgLyoqIFJlc29sdmUgdGhlIGF1dGhlbnRpY2F0ZWQgZW1haWwgZm9yIGEgcmVxdWVzdCwgb3IgYHVuZGVmaW5lZGAuICovXG4gIGFzeW5jIGF1dGhlbnRpY2F0ZShyZXE6IFJlcXVlc3QpOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICAgIC8vIFJldXNlIHNoYXJlZC1jb3JlJ3MgY29va2llIHBhcnNlciAoYWNjZXB0cyBhbiBFeHByZXNzIHJlcSBkaXJlY3RseSkgcmF0aGVyXG4gICAgLy8gdGhhbiByZS1pbXBsZW1lbnRpbmcgaGVhZGVyIHNwbGl0dGluZyBoZXJlLlxuICAgIGNvbnN0IHRva2VuID0gaHR0cC5wYXJzZUNvb2tpZXMocmVxKVtTRVNTSU9OX0NPT0tJRV07XG4gICAgcmV0dXJuIHZlcmlmeVNlc3Npb24odG9rZW4pO1xuICB9XG5cbiAgLyoqIFRoZSBgR0VUIC9hdXRoL3N0YXR1c2AgcGF5bG9hZCBmb3IgYSByZXF1ZXN0LiAqL1xuICBhc3luYyBzdGF0dXMocmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxBdXRoU3RhdHVzPiB7XG4gICAgY29uc3QgZW1haWwgPSBhd2FpdCB0aGlzLmF1dGhlbnRpY2F0ZShyZXEpO1xuICAgIHJldHVybiB7IGF1dGhlbnRpY2F0ZWQ6IEJvb2xlYW4oZW1haWwpLCBlbWFpbCwgZW5hYmxlZDogdHJ1ZSB9O1xuICB9XG5cbiAgLyoqXG4gICAqIEV4cHJlc3MgbWlkZGxld2FyZSBnYXRpbmcgcmVxdWVzdHMgb24gdGhlIEFQUCByb3V0ZXIgKG1vdW50ZWQgYXQgdGhlIGFwcFxuICAgKiByb290KS4gUmVxdWVzdHMgdG8gdGhlIGxvZ2luIGZsb3cgKGA8ZW1haWxCYXNlPi9hdXRoLypgKSBwYXNzIHRocm91Z2ggc28gYVxuICAgKiBjYWxsZXIgY2FuIGF1dGhlbnRpY2F0ZTsgdGhlIGFwcCdzIG93biBBUEkvYXNzZXQgcmVxdWVzdHMgbmVlZCBhIHZhbGlkXG4gICAqIHNlc3Npb24gb3IgZ2V0IGEgNDAxLiBgZW1haWxCYXNlYCBpcyB0aGUgcGx1Z2luJ3MgbW91bnQgcGF0aCAoZS5nLlxuICAgKiBgL2FwaS9lbWFpbGApIHNvIHRoZSBvcGVuIHByZWZpeCBpcyBtYXRjaGVkIGFnYWluc3QgdGhlIGZ1bGwgcmVxdWVzdCBwYXRoLlxuICAgKi9cbiAgbWlkZGxld2FyZShlbWFpbEJhc2U6IHN0cmluZyk6IChyZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UsIG5leHQ6IE5leHRGdW5jdGlvbikgPT4gdm9pZCB7XG4gICAgY29uc3Qgb3BlblByZWZpeCA9IGAke2VtYWlsQmFzZX0ke0FVVEhfUEFUSF9QUkVGSVh9YDtcbiAgICByZXR1cm4gKHJlcSwgcmVzLCBuZXh0KSA9PiB7XG4gICAgICBpZiAocmVxLnBhdGguc3RhcnRzV2l0aChvcGVuUHJlZml4KSkge1xuICAgICAgICBuZXh0KCk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIHZvaWQgdGhpcy5hdXRoZW50aWNhdGUocmVxKS50aGVuKChlbWFpbCkgPT4ge1xuICAgICAgICBpZiAoZW1haWwpIHtcbiAgICAgICAgICBuZXh0KCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmVzLnN0YXR1cyg0MDEpLmpzb24oeyBlcnJvcjogXCJhdXRoZW50aWNhdGlvbiByZXF1aXJlZFwiLCBsb2dpblBhdGg6IG9wZW5QcmVmaXggfSk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgIH07XG4gIH1cbn1cbiJdfQ==
@@ -0,0 +1,49 @@
1
+ /**
2
+ * One-time-code store + session JWT for the email-OTP gate.
3
+ *
4
+ * Two pieces:
5
+ *
6
+ * - **Code store** - a 6-digit code is generated with `crypto.randomInt` and
7
+ * kept server-side as a SHA-256 hash with an expiry and an attempt counter
8
+ * (never the plaintext, never in the JWT). `verifyCode` is constant-time on
9
+ * the hash, enforces the TTL, and burns the code after too many attempts or
10
+ * one success. In-memory `Map` keyed by lowercased email - fine for a
11
+ * single-instance app behind a tunnel.
12
+ * - **Session JWT** - on a correct code, `signSession` mints a short-lived
13
+ * HS256 JWT (via `jose`) carrying only the email; `verifySession` validates
14
+ * it. The signing key comes from `AUTH_JWT_SECRET`; when unset the gate
15
+ * FAILS OPEN with an ephemeral per-process key (sessions reset on restart)
16
+ * rather than refusing service - a Databricks App is already access-limited,
17
+ * so an unset secret degrades to "sessions don't survive restarts", not
18
+ * "nobody can log in".
19
+ *
20
+ * @module
21
+ */
22
+ /** Result of {@link CodeStore.verify}. */
23
+ export type VerifyOutcome = "ok" | "invalid" | "expired" | "too-many-attempts";
24
+ /** In-memory store of pending one-time codes, keyed by lowercased email. */
25
+ export declare class CodeStore {
26
+ private readonly ttlMs;
27
+ private readonly maxAttempts;
28
+ private readonly codes;
29
+ constructor(ttlMs: number, maxAttempts: number);
30
+ /**
31
+ * Generate, store (hashed), and RETURN a fresh 6-digit code for `email`. The
32
+ * caller emails the returned plaintext; only the hash is retained. Replaces
33
+ * any pending code for the address.
34
+ */
35
+ issue(email: string, now?: number): string;
36
+ /**
37
+ * Check `code` for `email`. Consumes the entry on success or when attempts are
38
+ * exhausted, so a code is single-use and can't be brute-forced past the cap.
39
+ */
40
+ verify(email: string, code: string, now?: number): VerifyOutcome;
41
+ /** Drop every pending code (tests). */
42
+ clear(): void;
43
+ }
44
+ /** Reset the memoized key (tests, or after changing the env in-process). */
45
+ export declare function resetSigningKey(): void;
46
+ /** Mint a short-lived session JWT for `email`, expiring in `ttlSeconds`. */
47
+ export declare function signSession(email: string, ttlSeconds: number): Promise<string>;
48
+ /** Validate a session JWT, returning the email it was minted for, or `undefined`. */
49
+ export declare function verifySession(token: string | undefined): Promise<string | undefined>;