@absolutejs/auth 0.30.0-beta.1 → 0.30.0-beta.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.
- package/dist/plugins/denyDisposableEmail.d.ts +7 -0
- package/dist/plugins/discordAlert.d.ts +7 -0
- package/dist/plugins/geoBlock.d.ts +8 -0
- package/dist/plugins/index.d.ts +12 -0
- package/dist/plugins/index.js +183 -0
- package/dist/plugins/index.js.map +16 -0
- package/dist/plugins/pagerdutyAlert.d.ts +9 -0
- package/dist/plugins/posthogIdentify.d.ts +7 -0
- package/dist/plugins/slackAlert.d.ts +7 -0
- package/package.json +6 -2
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AuditEvent, AuditEventType, AuditSink } from '../audit/types';
|
|
2
|
+
export type DiscordAlertOptions = {
|
|
3
|
+
events?: readonly AuditEventType[];
|
|
4
|
+
formatContent?: (event: AuditEvent) => string;
|
|
5
|
+
webhookUrl: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const discordAlertPlugin: ({ events, formatContent, webhookUrl }: DiscordAlertOptions) => AuditSink;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type GeoBlockOptions = {
|
|
2
|
+
allowCountries: readonly string[];
|
|
3
|
+
denyCountries?: never;
|
|
4
|
+
} | {
|
|
5
|
+
allowCountries?: never;
|
|
6
|
+
denyCountries: readonly string[];
|
|
7
|
+
};
|
|
8
|
+
export declare const geoBlockPlugin: (options: GeoBlockOptions) => (headers: Record<string, string | undefined>) => boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { denyDisposableEmailPlugin } from './denyDisposableEmail';
|
|
2
|
+
export type { DenyDisposableEmailDecision } from './denyDisposableEmail';
|
|
3
|
+
export { discordAlertPlugin } from './discordAlert';
|
|
4
|
+
export type { DiscordAlertOptions } from './discordAlert';
|
|
5
|
+
export { geoBlockPlugin } from './geoBlock';
|
|
6
|
+
export type { GeoBlockOptions } from './geoBlock';
|
|
7
|
+
export { pagerdutyAlertPlugin } from './pagerdutyAlert';
|
|
8
|
+
export type { PagerDutyAlertOptions, PagerDutySeverity } from './pagerdutyAlert';
|
|
9
|
+
export { posthogIdentifyPlugin } from './posthogIdentify';
|
|
10
|
+
export type { PosthogIdentifyOptions } from './posthogIdentify';
|
|
11
|
+
export { slackAlertPlugin } from './slackAlert';
|
|
12
|
+
export type { SlackAlertOptions } from './slackAlert';
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/credentials/emailValidation.ts
|
|
3
|
+
import { resolveMx } from "dns/promises";
|
|
4
|
+
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
|
|
5
|
+
var DISPOSABLE_DOMAINS = new Set([
|
|
6
|
+
"10minutemail.com",
|
|
7
|
+
"fakeinbox.com",
|
|
8
|
+
"getnada.com",
|
|
9
|
+
"guerrillamail.com",
|
|
10
|
+
"mailinator.com",
|
|
11
|
+
"maildrop.cc",
|
|
12
|
+
"sharklasers.com",
|
|
13
|
+
"temp-mail.org",
|
|
14
|
+
"tempmail.com",
|
|
15
|
+
"throwaway.email",
|
|
16
|
+
"trashmail.com",
|
|
17
|
+
"yopmail.com"
|
|
18
|
+
]);
|
|
19
|
+
var domainOf = (email) => email.slice(email.lastIndexOf("@") + 1).toLowerCase();
|
|
20
|
+
var hasMxRecord = async (domain) => {
|
|
21
|
+
try {
|
|
22
|
+
return (await resolveMx(domain)).length > 0;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var isDisposableEmail = (email, extraDomains) => {
|
|
28
|
+
const domain = domainOf(email);
|
|
29
|
+
return DISPOSABLE_DOMAINS.has(domain) || extraDomains !== undefined && new Set(extraDomains).has(domain);
|
|
30
|
+
};
|
|
31
|
+
var validateEmailDeliverability = async (email, options) => {
|
|
32
|
+
const normalized = email.trim().toLowerCase();
|
|
33
|
+
if (!EMAIL_PATTERN.test(normalized)) {
|
|
34
|
+
return { ok: false, reason: "invalid_format" };
|
|
35
|
+
}
|
|
36
|
+
if (isDisposableEmail(normalized, options?.disposableDomains)) {
|
|
37
|
+
return { ok: false, reason: "disposable" };
|
|
38
|
+
}
|
|
39
|
+
if (options?.checkMx === true && !await hasMxRecord(domainOf(normalized))) {
|
|
40
|
+
return { ok: false, reason: "no_mx" };
|
|
41
|
+
}
|
|
42
|
+
return { ok: true };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/plugins/denyDisposableEmail.ts
|
|
46
|
+
var denyDisposableEmailPlugin = async (email) => {
|
|
47
|
+
const trimmed = email.trim().toLowerCase();
|
|
48
|
+
if (await isDisposableEmail(trimmed)) {
|
|
49
|
+
return { allow: false, reason: "disposable_email" };
|
|
50
|
+
}
|
|
51
|
+
return { allow: true };
|
|
52
|
+
};
|
|
53
|
+
// src/plugins/discordAlert.ts
|
|
54
|
+
var defaultContent = (event) => {
|
|
55
|
+
const when = new Date(event.at).toISOString();
|
|
56
|
+
const who = event.userId ?? event.ip ?? "unknown";
|
|
57
|
+
return `\uD83D\uDD10 **${event.type}** \u2014 ${who} at ${when}`;
|
|
58
|
+
};
|
|
59
|
+
var discordAlertPlugin = ({
|
|
60
|
+
events,
|
|
61
|
+
formatContent = defaultContent,
|
|
62
|
+
webhookUrl
|
|
63
|
+
}) => ({
|
|
64
|
+
append: async (event) => {
|
|
65
|
+
if (events !== undefined && !events.includes(event.type))
|
|
66
|
+
return;
|
|
67
|
+
await fetch(webhookUrl, {
|
|
68
|
+
body: JSON.stringify({ content: formatContent(event) }),
|
|
69
|
+
headers: { "content-type": "application/json" },
|
|
70
|
+
method: "POST"
|
|
71
|
+
}).catch(() => {
|
|
72
|
+
return;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
// src/plugins/geoBlock.ts
|
|
77
|
+
var readCountry = (headers) => headers["x-client-country"]?.toUpperCase() ?? headers["cf-ipcountry"]?.toUpperCase();
|
|
78
|
+
var geoBlockPlugin = (options) => {
|
|
79
|
+
const allow = options.allowCountries === undefined ? undefined : new Set(options.allowCountries.map((country) => country.toUpperCase()));
|
|
80
|
+
const deny = options.denyCountries === undefined ? undefined : new Set(options.denyCountries.map((country) => country.toUpperCase()));
|
|
81
|
+
return (headers) => {
|
|
82
|
+
const country = readCountry(headers);
|
|
83
|
+
if (country === undefined)
|
|
84
|
+
return false;
|
|
85
|
+
if (deny !== undefined)
|
|
86
|
+
return deny.has(country);
|
|
87
|
+
if (allow !== undefined)
|
|
88
|
+
return !allow.has(country);
|
|
89
|
+
return false;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
// src/plugins/pagerdutyAlert.ts
|
|
93
|
+
var PAGERDUTY_EVENTS_URL = "https://events.pagerduty.com/v2/enqueue";
|
|
94
|
+
var pagerdutyAlertPlugin = ({
|
|
95
|
+
events,
|
|
96
|
+
routingKey,
|
|
97
|
+
severity = "warning",
|
|
98
|
+
source = "absolutejs-auth"
|
|
99
|
+
}) => ({
|
|
100
|
+
append: async (event) => {
|
|
101
|
+
if (events !== undefined && !events.includes(event.type))
|
|
102
|
+
return;
|
|
103
|
+
await fetch(PAGERDUTY_EVENTS_URL, {
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
event_action: "trigger",
|
|
106
|
+
payload: {
|
|
107
|
+
custom_details: event.metadata ?? {},
|
|
108
|
+
severity,
|
|
109
|
+
source,
|
|
110
|
+
summary: `auth event: ${event.type} (user=${event.userId ?? "unknown"})`,
|
|
111
|
+
timestamp: new Date(event.at).toISOString()
|
|
112
|
+
},
|
|
113
|
+
routing_key: routingKey
|
|
114
|
+
}),
|
|
115
|
+
headers: { "content-type": "application/json" },
|
|
116
|
+
method: "POST"
|
|
117
|
+
}).catch(() => {
|
|
118
|
+
return;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
// src/plugins/posthogIdentify.ts
|
|
123
|
+
var DEFAULT_HOST = "https://us.i.posthog.com";
|
|
124
|
+
var posthogIdentifyPlugin = ({
|
|
125
|
+
host = DEFAULT_HOST,
|
|
126
|
+
projectApiKey,
|
|
127
|
+
properties = (event) => ({ ...event.metadata ?? {} })
|
|
128
|
+
}) => ({
|
|
129
|
+
append: async (event) => {
|
|
130
|
+
if (event.userId === undefined)
|
|
131
|
+
return;
|
|
132
|
+
await fetch(`${host}/capture/`, {
|
|
133
|
+
body: JSON.stringify({
|
|
134
|
+
api_key: projectApiKey,
|
|
135
|
+
distinct_id: event.userId,
|
|
136
|
+
event: "$identify",
|
|
137
|
+
properties: {
|
|
138
|
+
$set: properties(event),
|
|
139
|
+
$set_once: { first_seen_event: event.type }
|
|
140
|
+
},
|
|
141
|
+
timestamp: new Date(event.at).toISOString()
|
|
142
|
+
}),
|
|
143
|
+
headers: { "content-type": "application/json" },
|
|
144
|
+
method: "POST"
|
|
145
|
+
}).catch(() => {
|
|
146
|
+
return;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
// src/plugins/slackAlert.ts
|
|
151
|
+
var defaultFormat = (event) => {
|
|
152
|
+
const when = new Date(event.at).toISOString();
|
|
153
|
+
const who = event.userId ?? event.ip ?? "unknown";
|
|
154
|
+
return `\uD83D\uDD10 *${event.type}* \u2014 ${who} at ${when}`;
|
|
155
|
+
};
|
|
156
|
+
var slackAlertPlugin = ({
|
|
157
|
+
events,
|
|
158
|
+
formatMessage = defaultFormat,
|
|
159
|
+
webhookUrl
|
|
160
|
+
}) => ({
|
|
161
|
+
append: async (event) => {
|
|
162
|
+
if (events !== undefined && !events.includes(event.type))
|
|
163
|
+
return;
|
|
164
|
+
await fetch(webhookUrl, {
|
|
165
|
+
body: JSON.stringify({ text: formatMessage(event) }),
|
|
166
|
+
headers: { "content-type": "application/json" },
|
|
167
|
+
method: "POST"
|
|
168
|
+
}).catch(() => {
|
|
169
|
+
return;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
export {
|
|
174
|
+
slackAlertPlugin,
|
|
175
|
+
posthogIdentifyPlugin,
|
|
176
|
+
pagerdutyAlertPlugin,
|
|
177
|
+
geoBlockPlugin,
|
|
178
|
+
discordAlertPlugin,
|
|
179
|
+
denyDisposableEmailPlugin
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
//# debugId=671489C19A19639D64756E2164756E21
|
|
183
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/credentials/emailValidation.ts", "../src/plugins/denyDisposableEmail.ts", "../src/plugins/discordAlert.ts", "../src/plugins/geoBlock.ts", "../src/plugins/pagerdutyAlert.ts", "../src/plugins/posthogIdentify.ts", "../src/plugins/slackAlert.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { resolveMx } from 'node:dns/promises';\n\n// Email deliverability validation for sign-up — format, disposable-domain block, and an\n// optional MX check. A starter disposable list ships built-in; extend it with your own.\n\nexport type EmailValidationResult = {\n\tok: boolean;\n\treason?: 'disposable' | 'invalid_format' | 'no_mx';\n};\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/u;\n\nconst DISPOSABLE_DOMAINS = new Set([\n\t'10minutemail.com',\n\t'fakeinbox.com',\n\t'getnada.com',\n\t'guerrillamail.com',\n\t'mailinator.com',\n\t'maildrop.cc',\n\t'sharklasers.com',\n\t'temp-mail.org',\n\t'tempmail.com',\n\t'throwaway.email',\n\t'trashmail.com',\n\t'yopmail.com'\n]);\n\nconst domainOf = (email: string) =>\n\temail.slice(email.lastIndexOf('@') + 1).toLowerCase();\n\nconst hasMxRecord = async (domain: string) => {\n\ttry {\n\t\treturn (await resolveMx(domain)).length > 0;\n\t} catch {\n\t\treturn false;\n\t}\n};\n\n// Whether an email's domain is a known disposable/temporary provider (built-in list plus any\n// `extraDomains` you pass).\nexport const isDisposableEmail = (\n\temail: string,\n\textraDomains?: Iterable<string>\n) => {\n\tconst domain = domainOf(email);\n\n\treturn (\n\t\tDISPOSABLE_DOMAINS.has(domain) ||\n\t\t(extraDomains !== undefined && new Set(extraDomains).has(domain))\n\t);\n};\n\n// Validate an email for sign-up. With `checkMx`, also confirms the domain has MX records\n// (a network lookup). Wire it into your register flow before creating the user.\nexport const validateEmailDeliverability = async (\n\temail: string,\n\toptions?: { checkMx?: boolean; disposableDomains?: Iterable<string> }\n): Promise<EmailValidationResult> => {\n\tconst normalized = email.trim().toLowerCase();\n\tif (!EMAIL_PATTERN.test(normalized)) {\n\t\treturn { ok: false, reason: 'invalid_format' };\n\t}\n\tif (isDisposableEmail(normalized, options?.disposableDomains)) {\n\t\treturn { ok: false, reason: 'disposable' };\n\t}\n\tif (\n\t\toptions?.checkMx === true &&\n\t\t!(await hasMxRecord(domainOf(normalized)))\n\t) {\n\t\treturn { ok: false, reason: 'no_mx' };\n\t}\n\n\treturn { ok: true };\n};\n",
|
|
6
|
+
"// Tiny wrapper around the existing `isDisposableEmail` so it slots into the\n// CredentialsConfig.onCreateCredentialUser hook chain. Composes with whatever else the\n// consumer is doing in onCreateCredentialUser — call this first, fall through on pass.\n//\n// ~10 lines; mostly here as a concrete demonstration that \"plugin\" = \"named function\".\n\nimport { isDisposableEmail } from '../credentials/emailValidation';\n\nexport type DenyDisposableEmailDecision =\n\t| { allow: false; reason: string }\n\t| { allow: true };\n\nexport const denyDisposableEmailPlugin = async (\n\temail: string\n): Promise<DenyDisposableEmailDecision> => {\n\tconst trimmed = email.trim().toLowerCase();\n\tif (await isDisposableEmail(trimmed)) {\n\t\treturn { allow: false, reason: 'disposable_email' };\n\t}\n\n\treturn { allow: true };\n};\n",
|
|
7
|
+
"// Discord webhook plugin — same shape as slackAlert, slightly different payload.\n// ~20 lines; copy + modify if you want embeds, mentions, etc.\n\nimport type { AuditEvent, AuditEventType, AuditSink } from '../audit/types';\n\nexport type DiscordAlertOptions = {\n\tevents?: readonly AuditEventType[];\n\tformatContent?: (event: AuditEvent) => string;\n\twebhookUrl: string;\n};\n\nconst defaultContent = (event: AuditEvent) => {\n\tconst when = new Date(event.at).toISOString();\n\tconst who = event.userId ?? event.ip ?? 'unknown';\n\n\treturn `🔐 **${event.type}** — ${who} at ${when}`;\n};\n\nexport const discordAlertPlugin = ({\n\tevents,\n\tformatContent = defaultContent,\n\twebhookUrl\n}: DiscordAlertOptions): AuditSink => ({\n\tappend: async (event) => {\n\t\tif (events !== undefined && !events.includes(event.type)) return;\n\t\tawait fetch(webhookUrl, {\n\t\t\tbody: JSON.stringify({ content: formatContent(event) }),\n\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\tmethod: 'POST'\n\t\t}).catch(() => undefined);\n\t}\n});\n",
|
|
8
|
+
"// Geo-block plugin — gate credential login on the request's country (from\n// `x-client-country` or `cf-ipcountry`). Pair with `isMfaRequired` (force MFA in\n// blocked countries) OR fail closed by throwing in your own login handler.\n//\n// ~25 lines; one Set lookup.\n\nconst readCountry = (headers: Record<string, string | undefined>) =>\n\theaders['x-client-country']?.toUpperCase() ??\n\theaders['cf-ipcountry']?.toUpperCase();\n\nexport type GeoBlockOptions =\n\t| { allowCountries: readonly string[]; denyCountries?: never }\n\t| { allowCountries?: never; denyCountries: readonly string[] };\n\n// Returns `true` when the request should be BLOCKED (i.e. the user is in a deny-listed\n// country, or not in the allow-list). The consumer uses the return to either force\n// MFA via `isMfaRequired` or to reject the login outright.\nexport const geoBlockPlugin = (options: GeoBlockOptions) => {\n\tconst allow =\n\t\toptions.allowCountries === undefined\n\t\t\t? undefined\n\t\t\t: new Set(options.allowCountries.map((country) => country.toUpperCase()));\n\tconst deny =\n\t\toptions.denyCountries === undefined\n\t\t\t? undefined\n\t\t\t: new Set(options.denyCountries.map((country) => country.toUpperCase()));\n\n\treturn (headers: Record<string, string | undefined>) => {\n\t\tconst country = readCountry(headers);\n\t\tif (country === undefined) return false;\n\t\tif (deny !== undefined) return deny.has(country);\n\t\tif (allow !== undefined) return !allow.has(country);\n\n\t\treturn false;\n\t};\n};\n",
|
|
9
|
+
"// PagerDuty Events API v2 plugin. Posts a trigger event to a PagerDuty service —\n// pair with security-critical audit events (credentials_login_failed, mfa_challenge_failed,\n// impersonation_started) by passing `events: [...]`. Severity defaults to 'warning' but\n// most consumers wire 'critical' for these.\n//\n// Get a routing key from a PagerDuty integration: Service → Integrations → +Add → Events API v2.\n\nimport type { AuditEventType, AuditSink } from '../audit/types';\n\nconst PAGERDUTY_EVENTS_URL = 'https://events.pagerduty.com/v2/enqueue';\n\nexport type PagerDutySeverity = 'critical' | 'error' | 'info' | 'warning';\n\nexport type PagerDutyAlertOptions = {\n\tevents?: readonly AuditEventType[];\n\troutingKey: string;\n\tseverity?: PagerDutySeverity;\n\t// Optional source identifier (e.g. your service name) for grouping in PagerDuty.\n\tsource?: string;\n};\n\nexport const pagerdutyAlertPlugin = ({\n\tevents,\n\troutingKey,\n\tseverity = 'warning',\n\tsource = 'absolutejs-auth'\n}: PagerDutyAlertOptions): AuditSink => ({\n\tappend: async (event) => {\n\t\tif (events !== undefined && !events.includes(event.type)) return;\n\t\tawait fetch(PAGERDUTY_EVENTS_URL, {\n\t\t\tbody: JSON.stringify({\n\t\t\t\tevent_action: 'trigger',\n\t\t\t\tpayload: {\n\t\t\t\t\tcustom_details: event.metadata ?? {},\n\t\t\t\t\tseverity,\n\t\t\t\t\tsource,\n\t\t\t\t\tsummary: `auth event: ${event.type} (user=${event.userId ?? 'unknown'})`,\n\t\t\t\t\ttimestamp: new Date(event.at).toISOString()\n\t\t\t\t},\n\t\t\t\trouting_key: routingKey\n\t\t\t}),\n\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\tmethod: 'POST'\n\t\t}).catch(() => undefined);\n\t}\n});\n",
|
|
10
|
+
"// PostHog server-side identify. Pair with audit events that have a `userId` (register,\n// credentials_login, oauth_login, …) to push the user to PostHog with their\n// email/properties so server-side events tie back to the right person.\n//\n// ~25 lines; one POST per event. Drop into the audit chain via composition or use as\n// an `AuditSink` directly.\n\nimport type { AuditEvent, AuditSink } from '../audit/types';\n\nexport type PosthogIdentifyOptions = {\n\thost?: string; // defaults to PostHog Cloud US\n\tprojectApiKey: string;\n\t// Pull the properties to send from the audit event metadata + your own enrichment.\n\tproperties?: (event: AuditEvent) => Record<string, unknown>;\n};\n\nconst DEFAULT_HOST = 'https://us.i.posthog.com';\n\nexport const posthogIdentifyPlugin = ({\n\thost = DEFAULT_HOST,\n\tprojectApiKey,\n\tproperties = (event) => ({ ...(event.metadata ?? {}) })\n}: PosthogIdentifyOptions): AuditSink => ({\n\tappend: async (event) => {\n\t\tif (event.userId === undefined) return;\n\t\tawait fetch(`${host}/capture/`, {\n\t\t\tbody: JSON.stringify({\n\t\t\t\tapi_key: projectApiKey,\n\t\t\t\tdistinct_id: event.userId,\n\t\t\t\tevent: '$identify',\n\t\t\t\tproperties: {\n\t\t\t\t\t$set: properties(event),\n\t\t\t\t\t$set_once: { first_seen_event: event.type }\n\t\t\t\t},\n\t\t\t\ttimestamp: new Date(event.at).toISOString()\n\t\t\t}),\n\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\tmethod: 'POST'\n\t\t}).catch(() => undefined);\n\t}\n});\n",
|
|
11
|
+
"// Slack webhook plugin. Pair with `audit.onAuditEvent` OR drop into the audit chain\n// (it's a valid `AuditSink`) to post a one-line summary of chosen audit events to a\n// Slack channel webhook. Fire-and-forget, ~30 lines — copy + modify if you want a\n// different message shape.\n\nimport type { AuditEvent, AuditEventType, AuditSink } from '../audit/types';\n\nexport type SlackAlertOptions = {\n\t// Optional event-type allow-list. Without it, EVERY event posts — typically you\n\t// want to filter to security-relevant events like login failures + MFA failures.\n\tevents?: readonly AuditEventType[];\n\t// Build the Slack message body from the event. Default is one short line; override\n\t// to use Block Kit, attachments, mentions, etc.\n\tformatMessage?: (event: AuditEvent) => string;\n\twebhookUrl: string;\n};\n\nconst defaultFormat = (event: AuditEvent) => {\n\tconst when = new Date(event.at).toISOString();\n\tconst who = event.userId ?? event.ip ?? 'unknown';\n\n\treturn `🔐 *${event.type}* — ${who} at ${when}`;\n};\n\nexport const slackAlertPlugin = ({\n\tevents,\n\tformatMessage = defaultFormat,\n\twebhookUrl\n}: SlackAlertOptions): AuditSink => ({\n\tappend: async (event) => {\n\t\tif (events !== undefined && !events.includes(event.type)) return;\n\t\tawait fetch(webhookUrl, {\n\t\t\tbody: JSON.stringify({ text: formatMessage(event) }),\n\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\tmethod: 'POST'\n\t\t}).catch(() => undefined);\n\t}\n});\n"
|
|
12
|
+
],
|
|
13
|
+
"mappings": ";;AAAA;AAUA,IAAM,gBAAgB;AAEtB,IAAM,qBAAqB,IAAI,IAAI;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAC;AAED,IAAM,WAAW,CAAC,UACjB,MAAM,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,EAAE,YAAY;AAErD,IAAM,cAAc,OAAO,WAAmB;AAAA,EAC7C,IAAI;AAAA,IACH,QAAQ,MAAM,UAAU,MAAM,GAAG,SAAS;AAAA,IACzC,MAAM;AAAA,IACP,OAAO;AAAA;AAAA;AAMF,IAAM,oBAAoB,CAChC,OACA,iBACI;AAAA,EACJ,MAAM,SAAS,SAAS,KAAK;AAAA,EAE7B,OACC,mBAAmB,IAAI,MAAM,KAC5B,iBAAiB,aAAa,IAAI,IAAI,YAAY,EAAE,IAAI,MAAM;AAAA;AAM1D,IAAM,8BAA8B,OAC1C,OACA,YACoC;AAAA,EACpC,MAAM,aAAa,MAAM,KAAK,EAAE,YAAY;AAAA,EAC5C,IAAI,CAAC,cAAc,KAAK,UAAU,GAAG;AAAA,IACpC,OAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAAA,EAC9C;AAAA,EACA,IAAI,kBAAkB,YAAY,SAAS,iBAAiB,GAAG;AAAA,IAC9D,OAAO,EAAE,IAAI,OAAO,QAAQ,aAAa;AAAA,EAC1C;AAAA,EACA,IACC,SAAS,YAAY,QACrB,CAAE,MAAM,YAAY,SAAS,UAAU,CAAC,GACvC;AAAA,IACD,OAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ;AAAA,EACrC;AAAA,EAEA,OAAO,EAAE,IAAI,KAAK;AAAA;;;AC5DZ,IAAM,4BAA4B,OACxC,UAC0C;AAAA,EAC1C,MAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AAAA,EACzC,IAAI,MAAM,kBAAkB,OAAO,GAAG;AAAA,IACrC,OAAO,EAAE,OAAO,OAAO,QAAQ,mBAAmB;AAAA,EACnD;AAAA,EAEA,OAAO,EAAE,OAAO,KAAK;AAAA;;ACTtB,IAAM,iBAAiB,CAAC,UAAsB;AAAA,EAC7C,MAAM,OAAO,IAAI,KAAK,MAAM,EAAE,EAAE,YAAY;AAAA,EAC5C,MAAM,MAAM,MAAM,UAAU,MAAM,MAAM;AAAA,EAExC,OAAO,kBAAO,MAAM,iBAAY,UAAU;AAAA;AAGpC,IAAM,qBAAqB;AAAA,EACjC;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,OACsC;AAAA,EACtC,QAAQ,OAAO,UAAU;AAAA,IACxB,IAAI,WAAW,aAAa,CAAC,OAAO,SAAS,MAAM,IAAI;AAAA,MAAG;AAAA,IAC1D,MAAM,MAAM,YAAY;AAAA,MACvB,MAAM,KAAK,UAAU,EAAE,SAAS,cAAc,KAAK,EAAE,CAAC;AAAA,MACtD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,QAAQ;AAAA,IACT,CAAC,EAAE,MAAM,MAAG;AAAA,MAAG;AAAA,KAAS;AAAA;AAE1B;;ACzBA,IAAM,cAAc,CAAC,YACpB,QAAQ,qBAAqB,YAAY,KACzC,QAAQ,iBAAiB,YAAY;AAS/B,IAAM,iBAAiB,CAAC,YAA6B;AAAA,EAC3D,MAAM,QACL,QAAQ,mBAAmB,YACxB,YACA,IAAI,IAAI,QAAQ,eAAe,IAAI,CAAC,YAAY,QAAQ,YAAY,CAAC,CAAC;AAAA,EAC1E,MAAM,OACL,QAAQ,kBAAkB,YACvB,YACA,IAAI,IAAI,QAAQ,cAAc,IAAI,CAAC,YAAY,QAAQ,YAAY,CAAC,CAAC;AAAA,EAEzE,OAAO,CAAC,YAAgD;AAAA,IACvD,MAAM,UAAU,YAAY,OAAO;AAAA,IACnC,IAAI,YAAY;AAAA,MAAW,OAAO;AAAA,IAClC,IAAI,SAAS;AAAA,MAAW,OAAO,KAAK,IAAI,OAAO;AAAA,IAC/C,IAAI,UAAU;AAAA,MAAW,OAAO,CAAC,MAAM,IAAI,OAAO;AAAA,IAElD,OAAO;AAAA;AAAA;;ACxBT,IAAM,uBAAuB;AAYtB,IAAM,uBAAuB;AAAA,EACnC;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,SAAS;AAAA,OAC+B;AAAA,EACxC,QAAQ,OAAO,UAAU;AAAA,IACxB,IAAI,WAAW,aAAa,CAAC,OAAO,SAAS,MAAM,IAAI;AAAA,MAAG;AAAA,IAC1D,MAAM,MAAM,sBAAsB;AAAA,MACjC,MAAM,KAAK,UAAU;AAAA,QACpB,cAAc;AAAA,QACd,SAAS;AAAA,UACR,gBAAgB,MAAM,YAAY,CAAC;AAAA,UACnC;AAAA,UACA;AAAA,UACA,SAAS,eAAe,MAAM,cAAc,MAAM,UAAU;AAAA,UAC5D,WAAW,IAAI,KAAK,MAAM,EAAE,EAAE,YAAY;AAAA,QAC3C;AAAA,QACA,aAAa;AAAA,MACd,CAAC;AAAA,MACD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,QAAQ;AAAA,IACT,CAAC,EAAE,MAAM,MAAG;AAAA,MAAG;AAAA,KAAS;AAAA;AAE1B;;AC7BA,IAAM,eAAe;AAEd,IAAM,wBAAwB;AAAA,EACpC,OAAO;AAAA,EACP;AAAA,EACA,aAAa,CAAC,WAAW,KAAM,MAAM,YAAY,CAAC,EAAG;AAAA,OACZ;AAAA,EACzC,QAAQ,OAAO,UAAU;AAAA,IACxB,IAAI,MAAM,WAAW;AAAA,MAAW;AAAA,IAChC,MAAM,MAAM,GAAG,iBAAiB;AAAA,MAC/B,MAAM,KAAK,UAAU;AAAA,QACpB,SAAS;AAAA,QACT,aAAa,MAAM;AAAA,QACnB,OAAO;AAAA,QACP,YAAY;AAAA,UACX,MAAM,WAAW,KAAK;AAAA,UACtB,WAAW,EAAE,kBAAkB,MAAM,KAAK;AAAA,QAC3C;AAAA,QACA,WAAW,IAAI,KAAK,MAAM,EAAE,EAAE,YAAY;AAAA,MAC3C,CAAC;AAAA,MACD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,QAAQ;AAAA,IACT,CAAC,EAAE,MAAM,MAAG;AAAA,MAAG;AAAA,KAAS;AAAA;AAE1B;;ACvBA,IAAM,gBAAgB,CAAC,UAAsB;AAAA,EAC5C,MAAM,OAAO,IAAI,KAAK,MAAM,EAAE,EAAE,YAAY;AAAA,EAC5C,MAAM,MAAM,MAAM,UAAU,MAAM,MAAM;AAAA,EAExC,OAAO,iBAAM,MAAM,gBAAW,UAAU;AAAA;AAGlC,IAAM,mBAAmB;AAAA,EAC/B;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,OACoC;AAAA,EACpC,QAAQ,OAAO,UAAU;AAAA,IACxB,IAAI,WAAW,aAAa,CAAC,OAAO,SAAS,MAAM,IAAI;AAAA,MAAG;AAAA,IAC1D,MAAM,MAAM,YAAY;AAAA,MACvB,MAAM,KAAK,UAAU,EAAE,MAAM,cAAc,KAAK,EAAE,CAAC;AAAA,MACnD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,QAAQ;AAAA,IACT,CAAC,EAAE,MAAM,MAAG;AAAA,MAAG;AAAA,KAAS;AAAA;AAE1B;",
|
|
14
|
+
"debugId": "671489C19A19639D64756E2164756E21",
|
|
15
|
+
"names": []
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AuditEventType, AuditSink } from '../audit/types';
|
|
2
|
+
export type PagerDutySeverity = 'critical' | 'error' | 'info' | 'warning';
|
|
3
|
+
export type PagerDutyAlertOptions = {
|
|
4
|
+
events?: readonly AuditEventType[];
|
|
5
|
+
routingKey: string;
|
|
6
|
+
severity?: PagerDutySeverity;
|
|
7
|
+
source?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const pagerdutyAlertPlugin: ({ events, routingKey, severity, source }: PagerDutyAlertOptions) => AuditSink;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AuditEvent, AuditSink } from '../audit/types';
|
|
2
|
+
export type PosthogIdentifyOptions = {
|
|
3
|
+
host?: string;
|
|
4
|
+
projectApiKey: string;
|
|
5
|
+
properties?: (event: AuditEvent) => Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
export declare const posthogIdentifyPlugin: ({ host, projectApiKey, properties }: PosthogIdentifyOptions) => AuditSink;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AuditEvent, AuditEventType, AuditSink } from '../audit/types';
|
|
2
|
+
export type SlackAlertOptions = {
|
|
3
|
+
events?: readonly AuditEventType[];
|
|
4
|
+
formatMessage?: (event: AuditEvent) => string;
|
|
5
|
+
webhookUrl: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const slackAlertPlugin: ({ events, formatMessage, webhookUrl }: SlackAlertOptions) => AuditSink;
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.30.0-beta.
|
|
2
|
+
"version": "0.30.0-beta.2",
|
|
3
3
|
"name": "@absolutejs/auth",
|
|
4
4
|
"description": "An authorization library for absolutejs",
|
|
5
5
|
"repository": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"license": "CC BY-NC 4.0",
|
|
11
11
|
"author": "Alex Kahn",
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "rm -rf dist && bun build src/index.ts src/htmx/index.ts src/client/index.ts src/client/react.ts --outdir dist --sourcemap --target=bun --external elysia --external react && tsc --emitDeclarationOnly --project tsconfig.json",
|
|
13
|
+
"build": "rm -rf dist && bun build src/index.ts src/htmx/index.ts src/client/index.ts src/client/react.ts src/plugins/index.ts --outdir dist --sourcemap --target=bun --external elysia --external react && tsc --emitDeclarationOnly --project tsconfig.json",
|
|
14
14
|
"config": "absolute config",
|
|
15
15
|
"test": "bun test",
|
|
16
16
|
"format": "absolute prettier --write",
|
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
"import": "./dist/htmx/index.js",
|
|
74
74
|
"types": "./dist/htmx/index.d.ts"
|
|
75
75
|
},
|
|
76
|
+
"./plugins": {
|
|
77
|
+
"import": "./dist/plugins/index.js",
|
|
78
|
+
"types": "./dist/plugins/index.d.ts"
|
|
79
|
+
},
|
|
76
80
|
"./react": {
|
|
77
81
|
"import": "./dist/client/react.js",
|
|
78
82
|
"types": "./dist/client/react.d.ts"
|