@absolutejs/auth 0.27.0-beta.1 → 0.27.0-beta.3
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/abuse/config.d.ts +29 -0
- package/dist/audit/integrity.d.ts +15 -0
- package/dist/audit/siem.d.ts +11 -0
- package/dist/audit/types.d.ts +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +340 -4
- package/dist/index.js.map +13 -8
- package/dist/mfa/rotation.d.ts +17 -0
- package/dist/mfa/types.d.ts +1 -0
- package/dist/session/impersonation.d.ts +29 -0
- package/dist/session/promote.d.ts +2 -1
- package/dist/types.d.ts +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type AbuseAction = 'allow' | 'challenge' | 'deny';
|
|
2
|
+
export type AbuseSignal = 'blocked_ip' | 'bot' | 'captcha_failed' | 'not_allowlisted';
|
|
3
|
+
export type BotClass = 'agent' | 'bot' | 'crawler' | 'human';
|
|
4
|
+
export type AbuseContext = {
|
|
5
|
+
captchaToken?: string;
|
|
6
|
+
ip?: string;
|
|
7
|
+
userAgent?: string;
|
|
8
|
+
};
|
|
9
|
+
export type AbuseReason = {
|
|
10
|
+
action: AbuseAction;
|
|
11
|
+
signal: AbuseSignal;
|
|
12
|
+
};
|
|
13
|
+
export type AbuseAssessment = {
|
|
14
|
+
action: AbuseAction;
|
|
15
|
+
reasons: AbuseReason[];
|
|
16
|
+
};
|
|
17
|
+
export type AbuseConfig = {
|
|
18
|
+
botAction?: AbuseAction;
|
|
19
|
+
captchaAction?: AbuseAction;
|
|
20
|
+
classifyBot?: (context: AbuseContext) => BotClass | Promise<BotClass>;
|
|
21
|
+
ipAllow?: string[];
|
|
22
|
+
ipDeny?: string[];
|
|
23
|
+
verifyCaptcha?: (token: string | undefined, context: AbuseContext) => boolean | Promise<boolean>;
|
|
24
|
+
};
|
|
25
|
+
export declare const assessAbuse: (config: AbuseConfig, context: AbuseContext) => Promise<AbuseAssessment>;
|
|
26
|
+
export declare const createAbuseGuard: (config: AbuseConfig) => {
|
|
27
|
+
assess: (context: AbuseContext) => Promise<AbuseAssessment>;
|
|
28
|
+
};
|
|
29
|
+
export declare const defaultBotClassifier: (context: AbuseContext) => "bot" | "crawler" | "human";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AuditEvent, AuditSink } from './types';
|
|
2
|
+
export type AuditIntegrity = {
|
|
3
|
+
hash: string;
|
|
4
|
+
previousHash: string;
|
|
5
|
+
};
|
|
6
|
+
export type AuditChainResult = {
|
|
7
|
+
brokenAt?: number;
|
|
8
|
+
ok: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare const createTamperEvidentSink: ({ secret, sink }: {
|
|
11
|
+
secret?: string;
|
|
12
|
+
sink: AuditSink;
|
|
13
|
+
}) => AuditSink;
|
|
14
|
+
export declare const hashAuditEvent: (event: AuditEvent, previousHash: string, secret?: string) => Promise<string>;
|
|
15
|
+
export declare const verifyAuditChain: (events: AuditEvent[], secret?: string) => Promise<AuditChainResult>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AuditSink } from './types';
|
|
2
|
+
export type SiemFormat = 'datadog' | 'generic' | 'splunk';
|
|
3
|
+
export type SiemEndpoint = {
|
|
4
|
+
format?: SiemFormat;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
token?: string;
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const createSiemLogStream: ({ endpoints }: {
|
|
10
|
+
endpoints: SiemEndpoint[];
|
|
11
|
+
}) => AuditSink;
|
package/dist/audit/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OrganizationId } from '../tenancy';
|
|
2
|
-
export type AuditEventType = 'account_deleted' | 'authorization_denied' | 'credentials_login' | 'credentials_login_failed' | 'data_exported' | 'email_verified' | 'identity_conflict' | 'invitation_accepted' | 'invitation_created' | 'logout' | 'membership_removed' | 'mfa_challenge' | 'mfa_challenge_failed' | 'mfa_enrolled' | 'oauth_login' | 'organization_created' | 'password_reset' | 'passwordless_login' | 'register' | 'role_assigned' | 'scim_provision' | 'scim_token_created' | 'session_revoked' | 'setup_session_created' | 'sso_connection_configured' | 'sso_login' | 'token_refreshed' | 'token_revoked' | 'webauthn_authenticated' | 'webauthn_registered';
|
|
2
|
+
export type AuditEventType = 'account_deleted' | 'authorization_denied' | 'credentials_login' | 'credentials_login_failed' | 'data_exported' | 'email_verified' | 'identity_conflict' | 'impersonation_ended' | 'impersonation_started' | 'invitation_accepted' | 'invitation_created' | 'logout' | 'membership_removed' | 'mfa_challenge' | 'mfa_challenge_failed' | 'mfa_enrolled' | 'oauth_login' | 'organization_created' | 'password_reset' | 'passwordless_login' | 'register' | 'role_assigned' | 'scim_provision' | 'scim_token_created' | 'session_revoked' | 'setup_session_created' | 'sso_connection_configured' | 'sso_login' | 'token_refreshed' | 'token_revoked' | 'webauthn_authenticated' | 'webauthn_registered';
|
|
3
3
|
export type AuditEvent = {
|
|
4
4
|
at: number;
|
|
5
5
|
ip?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -8407,6 +8407,7 @@ export { protectRoutePlugin } from './routes/protectRoute';
|
|
|
8407
8407
|
export { sessionRoutes } from './routes/sessions';
|
|
8408
8408
|
export { stepUpPlugin } from './routes/stepUp';
|
|
8409
8409
|
export * from './session/sessionsConfig';
|
|
8410
|
+
export { endImpersonation, isImpersonating, startImpersonation } from './session/impersonation';
|
|
8410
8411
|
export { listUserSessions, revokeUserSessions } from './session/userSessions';
|
|
8411
8412
|
export type { UserSession } from './session/userSessions';
|
|
8412
8413
|
export { sessionCleanup } from './session/cleanup';
|
|
@@ -8437,12 +8438,19 @@ export { consumeBackupCode, generateBackupCodes } from './mfa/backupCodes';
|
|
|
8437
8438
|
export { createMfaGate } from './mfa/gate';
|
|
8438
8439
|
export { mfaChallenge } from './mfa/challenge';
|
|
8439
8440
|
export { mfaRoutes } from './mfa/routes';
|
|
8441
|
+
export { rotateMfaEncryptionKey } from './mfa/rotation';
|
|
8442
|
+
export type { MfaKeyRotationResult } from './mfa/rotation';
|
|
8440
8443
|
export { mfaTotpRoutes } from './mfa/totp';
|
|
8441
8444
|
export { decryptTotpSecret, encryptTotpSecret } from './mfa/secret';
|
|
8442
8445
|
export { createInMemoryMfaStore } from './mfa/inMemoryMfaStore';
|
|
8443
8446
|
export { createNeonMfaStore, createPostgresMfaStore, mfaEnrollmentsTable } from './mfa/postgresMfaStore';
|
|
8444
8447
|
export * from './audit/config';
|
|
8445
8448
|
export * from './audit/types';
|
|
8449
|
+
export { createTamperEvidentSink, hashAuditEvent, verifyAuditChain } from './audit/integrity';
|
|
8450
|
+
export type { AuditChainResult, AuditIntegrity } from './audit/integrity';
|
|
8451
|
+
export { createSiemLogStream } from './audit/siem';
|
|
8452
|
+
export type { SiemEndpoint, SiemFormat } from './audit/siem';
|
|
8453
|
+
export * from './abuse/config';
|
|
8446
8454
|
export * from './authorization/config';
|
|
8447
8455
|
export { protectPermissionPlugin } from './authorization/protectPermission';
|
|
8448
8456
|
export * from './compliance/config';
|
package/dist/index.js
CHANGED
|
@@ -3215,6 +3215,7 @@ var persistWhen = async (shouldPersist, persist) => {
|
|
|
3215
3215
|
var promoteToSession = async ({
|
|
3216
3216
|
authSessionStore,
|
|
3217
3217
|
cookie,
|
|
3218
|
+
impersonator,
|
|
3218
3219
|
inMemorySession,
|
|
3219
3220
|
samlLogout,
|
|
3220
3221
|
sessionDurationMs,
|
|
@@ -3233,6 +3234,8 @@ var promoteToSession = async ({
|
|
|
3233
3234
|
};
|
|
3234
3235
|
if (samlLogout !== undefined)
|
|
3235
3236
|
data.samlLogout = samlLogout;
|
|
3237
|
+
if (impersonator !== undefined)
|
|
3238
|
+
data.impersonator = impersonator;
|
|
3236
3239
|
targetSession[userSessionId] = data;
|
|
3237
3240
|
cookie.set({
|
|
3238
3241
|
httpOnly: true,
|
|
@@ -17531,6 +17534,85 @@ var createInMemoryLinkedProviderStores = (input = {}) => {
|
|
|
17531
17534
|
};
|
|
17532
17535
|
return { bindingStore, grantStore };
|
|
17533
17536
|
};
|
|
17537
|
+
// src/session/impersonation.ts
|
|
17538
|
+
var DEFAULT_IMPERSONATION_TTL_MS = MILLISECONDS_IN_AN_HOUR;
|
|
17539
|
+
var endImpersonation = async ({
|
|
17540
|
+
authSessionStore,
|
|
17541
|
+
cookie,
|
|
17542
|
+
emit,
|
|
17543
|
+
inMemorySession
|
|
17544
|
+
}) => {
|
|
17545
|
+
const currentId = cookie.value;
|
|
17546
|
+
if (currentId === undefined)
|
|
17547
|
+
return { restored: false };
|
|
17548
|
+
const current = await loadSessionFromSource({
|
|
17549
|
+
authSessionStore,
|
|
17550
|
+
removeExpired: false,
|
|
17551
|
+
session: inMemorySession,
|
|
17552
|
+
userSessionId: currentId
|
|
17553
|
+
});
|
|
17554
|
+
const impersonator = current?.impersonator;
|
|
17555
|
+
if (authSessionStore)
|
|
17556
|
+
await authSessionStore.removeSession(currentId);
|
|
17557
|
+
else
|
|
17558
|
+
delete inMemorySession[currentId];
|
|
17559
|
+
await emit?.({
|
|
17560
|
+
at: Date.now(),
|
|
17561
|
+
metadata: impersonator === undefined ? undefined : { actorId: impersonator.actorId },
|
|
17562
|
+
type: "impersonation_ended"
|
|
17563
|
+
});
|
|
17564
|
+
const returnTo = impersonator?.returnToSessionId;
|
|
17565
|
+
const prior = returnTo === undefined ? undefined : await loadSessionFromSource({
|
|
17566
|
+
authSessionStore,
|
|
17567
|
+
session: inMemorySession,
|
|
17568
|
+
userSessionId: returnTo
|
|
17569
|
+
});
|
|
17570
|
+
if (prior !== undefined && returnTo !== undefined) {
|
|
17571
|
+
cookie.set({
|
|
17572
|
+
httpOnly: true,
|
|
17573
|
+
sameSite: "lax",
|
|
17574
|
+
secure: true,
|
|
17575
|
+
value: returnTo
|
|
17576
|
+
});
|
|
17577
|
+
return { restored: true };
|
|
17578
|
+
}
|
|
17579
|
+
cookie.remove();
|
|
17580
|
+
return { restored: false };
|
|
17581
|
+
};
|
|
17582
|
+
var isImpersonating = (session) => session?.impersonator !== undefined;
|
|
17583
|
+
var startImpersonation = async ({
|
|
17584
|
+
authSessionStore,
|
|
17585
|
+
cookie,
|
|
17586
|
+
emit,
|
|
17587
|
+
getUserId,
|
|
17588
|
+
impersonator,
|
|
17589
|
+
inMemorySession,
|
|
17590
|
+
sessionDurationMs = DEFAULT_IMPERSONATION_TTL_MS,
|
|
17591
|
+
user
|
|
17592
|
+
}) => {
|
|
17593
|
+
const stamp = {
|
|
17594
|
+
actorEmail: impersonator.actorEmail,
|
|
17595
|
+
actorId: impersonator.actorId,
|
|
17596
|
+
reason: impersonator.reason,
|
|
17597
|
+
returnToSessionId: cookie.value,
|
|
17598
|
+
startedAt: Date.now()
|
|
17599
|
+
};
|
|
17600
|
+
const sessionId = await promoteToSession({
|
|
17601
|
+
authSessionStore,
|
|
17602
|
+
cookie,
|
|
17603
|
+
impersonator: stamp,
|
|
17604
|
+
inMemorySession,
|
|
17605
|
+
sessionDurationMs,
|
|
17606
|
+
user
|
|
17607
|
+
});
|
|
17608
|
+
await emit?.({
|
|
17609
|
+
at: Date.now(),
|
|
17610
|
+
metadata: { actorId: stamp.actorId, reason: stamp.reason },
|
|
17611
|
+
type: "impersonation_started",
|
|
17612
|
+
userId: getUserId?.(user)
|
|
17613
|
+
});
|
|
17614
|
+
return sessionId;
|
|
17615
|
+
};
|
|
17534
17616
|
// src/utils.ts
|
|
17535
17617
|
var defineAuthConfig = (configuration) => configuration;
|
|
17536
17618
|
var defineAuthHtmxConfig = (htmxConfig) => htmxConfig;
|
|
@@ -17843,6 +17925,56 @@ var createPostgresCredentialStore = (db) => ({
|
|
|
17843
17925
|
await db.update(credentialsTable).set({ email_verified: true, updated_at_ms: Date.now() }).where(eq(credentialsTable.email, email.toLowerCase()));
|
|
17844
17926
|
}
|
|
17845
17927
|
});
|
|
17928
|
+
// src/mfa/rotation.ts
|
|
17929
|
+
var tryDecrypt = async (ciphertext, key) => {
|
|
17930
|
+
try {
|
|
17931
|
+
return await decryptTotpSecret(ciphertext, key);
|
|
17932
|
+
} catch {
|
|
17933
|
+
return;
|
|
17934
|
+
}
|
|
17935
|
+
};
|
|
17936
|
+
var ensureReadable = async (ciphertext, key, userId) => {
|
|
17937
|
+
if (await tryDecrypt(ciphertext, key) === undefined) {
|
|
17938
|
+
throw new Error(`TOTP secret for ${userId} decrypts with neither the old nor the new key`);
|
|
17939
|
+
}
|
|
17940
|
+
};
|
|
17941
|
+
var planEnrollment = async (enrollment, oldKey, newKey) => {
|
|
17942
|
+
const ciphertext = enrollment.totpSecretCiphertext;
|
|
17943
|
+
if (ciphertext === undefined || ciphertext.length === 0) {
|
|
17944
|
+
const plan2 = { kind: "skip" };
|
|
17945
|
+
return plan2;
|
|
17946
|
+
}
|
|
17947
|
+
const secret = await tryDecrypt(ciphertext, oldKey);
|
|
17948
|
+
if (secret === undefined) {
|
|
17949
|
+
await ensureReadable(ciphertext, newKey, enrollment.userId);
|
|
17950
|
+
const plan2 = { kind: "already" };
|
|
17951
|
+
return plan2;
|
|
17952
|
+
}
|
|
17953
|
+
const plan = {
|
|
17954
|
+
enrollment: {
|
|
17955
|
+
...enrollment,
|
|
17956
|
+
totpSecretCiphertext: await encryptTotpSecret(secret, newKey),
|
|
17957
|
+
updatedAt: Date.now()
|
|
17958
|
+
},
|
|
17959
|
+
kind: "rotate"
|
|
17960
|
+
};
|
|
17961
|
+
return plan;
|
|
17962
|
+
};
|
|
17963
|
+
var rotateMfaEncryptionKey = async ({
|
|
17964
|
+
mfaStore,
|
|
17965
|
+
newKey,
|
|
17966
|
+
oldKey
|
|
17967
|
+
}) => {
|
|
17968
|
+
const enrollments = await mfaStore.listEnrollments();
|
|
17969
|
+
const plans = await Promise.all(enrollments.map((enrollment) => planEnrollment(enrollment, oldKey, newKey)));
|
|
17970
|
+
await Promise.all(plans.map((plan) => plan.kind === "rotate" ? mfaStore.saveEnrollment(plan.enrollment) : Promise.resolve()));
|
|
17971
|
+
return {
|
|
17972
|
+
alreadyRotated: plans.filter((plan) => plan.kind === "already").length,
|
|
17973
|
+
rotated: plans.filter((plan) => plan.kind === "rotate").length,
|
|
17974
|
+
skippedNoSecret: plans.filter((plan) => plan.kind === "skip").length,
|
|
17975
|
+
total: plans.length
|
|
17976
|
+
};
|
|
17977
|
+
};
|
|
17846
17978
|
// src/mfa/inMemoryMfaStore.ts
|
|
17847
17979
|
var cloneEnrollment = (value) => ({
|
|
17848
17980
|
...value,
|
|
@@ -17855,6 +17987,7 @@ var createInMemoryMfaStore = () => {
|
|
|
17855
17987
|
const enrollment = enrollments.get(userId);
|
|
17856
17988
|
return enrollment ? cloneEnrollment(enrollment) : undefined;
|
|
17857
17989
|
},
|
|
17990
|
+
listEnrollments: async () => Array.from(enrollments.values()).map(cloneEnrollment),
|
|
17858
17991
|
removeEnrollment: async (userId) => {
|
|
17859
17992
|
enrollments.delete(userId);
|
|
17860
17993
|
},
|
|
@@ -17889,6 +18022,10 @@ var createPostgresMfaStore = (db) => ({
|
|
|
17889
18022
|
const [row] = await db.select().from(mfaEnrollmentsTable).where(eq(mfaEnrollmentsTable.user_id, userId)).limit(1);
|
|
17890
18023
|
return row ? toEnrollment(row) : undefined;
|
|
17891
18024
|
},
|
|
18025
|
+
listEnrollments: async () => {
|
|
18026
|
+
const rows = await db.select().from(mfaEnrollmentsTable);
|
|
18027
|
+
return rows.map(toEnrollment);
|
|
18028
|
+
},
|
|
17892
18029
|
removeEnrollment: async (userId) => {
|
|
17893
18030
|
await db.delete(mfaEnrollmentsTable).where(eq(mfaEnrollmentsTable.user_id, userId));
|
|
17894
18031
|
},
|
|
@@ -17908,6 +18045,194 @@ var createPostgresMfaStore = (db) => ({
|
|
|
17908
18045
|
});
|
|
17909
18046
|
}
|
|
17910
18047
|
});
|
|
18048
|
+
// src/audit/integrity.ts
|
|
18049
|
+
var INTEGRITY_KEY = "__integrity";
|
|
18050
|
+
var GENESIS = "";
|
|
18051
|
+
var HEX_RADIX2 = 16;
|
|
18052
|
+
var HEX_PAD = 2;
|
|
18053
|
+
var encoder = new TextEncoder;
|
|
18054
|
+
var toHex = (buffer) => [...new Uint8Array(buffer)].map((byte) => byte.toString(HEX_RADIX2).padStart(HEX_PAD, "0")).join("");
|
|
18055
|
+
var sha256Hex = async (message) => toHex(await crypto.subtle.digest("SHA-256", encoder.encode(message)));
|
|
18056
|
+
var hmacSha256Hex = async (secret, message) => {
|
|
18057
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { hash: "SHA-256", name: "HMAC" }, false, ["sign"]);
|
|
18058
|
+
return toHex(await crypto.subtle.sign("HMAC", key, encoder.encode(message)));
|
|
18059
|
+
};
|
|
18060
|
+
var sortKeys = (value) => value === null || typeof value !== "object" || Array.isArray(value) ? value : Object.fromEntries(Object.entries(value).sort((left, right) => left[0].localeCompare(right[0])));
|
|
18061
|
+
var stableStringify = (value) => JSON.stringify(value, (_key, val) => sortKeys(val));
|
|
18062
|
+
var cleanMetadata = (metadata) => {
|
|
18063
|
+
if (metadata === undefined)
|
|
18064
|
+
return;
|
|
18065
|
+
const result = {};
|
|
18066
|
+
for (const [key, val] of Object.entries(metadata)) {
|
|
18067
|
+
if (key !== INTEGRITY_KEY)
|
|
18068
|
+
result[key] = val;
|
|
18069
|
+
}
|
|
18070
|
+
return Object.keys(result).length === 0 ? undefined : result;
|
|
18071
|
+
};
|
|
18072
|
+
var readIntegrity = (event) => {
|
|
18073
|
+
const raw = event.metadata?.[INTEGRITY_KEY];
|
|
18074
|
+
if (raw === undefined)
|
|
18075
|
+
return;
|
|
18076
|
+
return raw;
|
|
18077
|
+
};
|
|
18078
|
+
var brokenAt = (index) => {
|
|
18079
|
+
const result = { brokenAt: index, ok: false };
|
|
18080
|
+
return result;
|
|
18081
|
+
};
|
|
18082
|
+
var createTamperEvidentSink = ({
|
|
18083
|
+
secret,
|
|
18084
|
+
sink
|
|
18085
|
+
}) => {
|
|
18086
|
+
let lastHash;
|
|
18087
|
+
const seed = async () => {
|
|
18088
|
+
if (lastHash !== undefined)
|
|
18089
|
+
return;
|
|
18090
|
+
const [recent] = await sink.list?.({ limit: 1 }) ?? [];
|
|
18091
|
+
lastHash = recent ? readIntegrity(recent)?.hash ?? GENESIS : GENESIS;
|
|
18092
|
+
};
|
|
18093
|
+
return {
|
|
18094
|
+
list: sink.list,
|
|
18095
|
+
append: async (event) => {
|
|
18096
|
+
await seed();
|
|
18097
|
+
const previousHash = lastHash ?? GENESIS;
|
|
18098
|
+
const hash = await hashAuditEvent(event, previousHash, secret);
|
|
18099
|
+
lastHash = hash;
|
|
18100
|
+
const integrity = { hash, previousHash };
|
|
18101
|
+
await sink.append({
|
|
18102
|
+
...event,
|
|
18103
|
+
metadata: { ...event.metadata, [INTEGRITY_KEY]: integrity }
|
|
18104
|
+
});
|
|
18105
|
+
}
|
|
18106
|
+
};
|
|
18107
|
+
};
|
|
18108
|
+
var hashAuditEvent = async (event, previousHash, secret) => {
|
|
18109
|
+
const message = `${previousHash}.${stableStringify({
|
|
18110
|
+
...event,
|
|
18111
|
+
metadata: cleanMetadata(event.metadata)
|
|
18112
|
+
})}`;
|
|
18113
|
+
return secret === undefined ? sha256Hex(message) : hmacSha256Hex(secret, message);
|
|
18114
|
+
};
|
|
18115
|
+
var verifyAuditChain = async (events, secret) => {
|
|
18116
|
+
let previousHash = GENESIS;
|
|
18117
|
+
for (let index = 0;index < events.length; index += 1) {
|
|
18118
|
+
const event = events[index];
|
|
18119
|
+
if (event === undefined)
|
|
18120
|
+
return brokenAt(index);
|
|
18121
|
+
const integrity = readIntegrity(event);
|
|
18122
|
+
const expected = await hashAuditEvent(event, previousHash, secret);
|
|
18123
|
+
if (integrity === undefined || integrity.previousHash !== previousHash || integrity.hash !== expected) {
|
|
18124
|
+
return brokenAt(index);
|
|
18125
|
+
}
|
|
18126
|
+
previousHash = integrity.hash;
|
|
18127
|
+
}
|
|
18128
|
+
const valid = { ok: true };
|
|
18129
|
+
return valid;
|
|
18130
|
+
};
|
|
18131
|
+
// src/audit/siem.ts
|
|
18132
|
+
var SOURCE = "absolutejs-auth";
|
|
18133
|
+
var requestFor = (endpoint, event) => {
|
|
18134
|
+
const base = {
|
|
18135
|
+
"content-type": "application/json",
|
|
18136
|
+
...endpoint.headers
|
|
18137
|
+
};
|
|
18138
|
+
if (endpoint.format === "datadog") {
|
|
18139
|
+
return {
|
|
18140
|
+
body: JSON.stringify({
|
|
18141
|
+
...event,
|
|
18142
|
+
ddsource: SOURCE,
|
|
18143
|
+
service: SOURCE
|
|
18144
|
+
}),
|
|
18145
|
+
headers: endpoint.token === undefined ? base : { ...base, "DD-API-KEY": endpoint.token }
|
|
18146
|
+
};
|
|
18147
|
+
}
|
|
18148
|
+
if (endpoint.format === "splunk") {
|
|
18149
|
+
return {
|
|
18150
|
+
body: JSON.stringify({ event, sourcetype: SOURCE }),
|
|
18151
|
+
headers: endpoint.token === undefined ? base : { ...base, authorization: `Splunk ${endpoint.token}` }
|
|
18152
|
+
};
|
|
18153
|
+
}
|
|
18154
|
+
return {
|
|
18155
|
+
body: JSON.stringify(event),
|
|
18156
|
+
headers: endpoint.token === undefined ? base : { ...base, authorization: `Bearer ${endpoint.token}` }
|
|
18157
|
+
};
|
|
18158
|
+
};
|
|
18159
|
+
var createSiemLogStream = ({
|
|
18160
|
+
endpoints
|
|
18161
|
+
}) => ({
|
|
18162
|
+
append: async (event) => {
|
|
18163
|
+
await Promise.all(endpoints.map(async (endpoint) => {
|
|
18164
|
+
const { body, headers } = requestFor(endpoint, event);
|
|
18165
|
+
await fetch(endpoint.url, {
|
|
18166
|
+
body,
|
|
18167
|
+
headers,
|
|
18168
|
+
method: "POST"
|
|
18169
|
+
}).catch(() => {
|
|
18170
|
+
return;
|
|
18171
|
+
});
|
|
18172
|
+
}));
|
|
18173
|
+
}
|
|
18174
|
+
});
|
|
18175
|
+
// src/abuse/config.ts
|
|
18176
|
+
var IPV4_BITS = 32;
|
|
18177
|
+
var IPV4_OCTET_SPACE = 256;
|
|
18178
|
+
var FULL_MASK = -1;
|
|
18179
|
+
var ACTION_SEVERITY = {
|
|
18180
|
+
allow: 0,
|
|
18181
|
+
challenge: 1,
|
|
18182
|
+
deny: 2
|
|
18183
|
+
};
|
|
18184
|
+
var BOT_PATTERN = /bot|crawl|spider|curl|wget|python-requests|headless|scrapy/iu;
|
|
18185
|
+
var CRAWLER_PATTERN = /googlebot|bingbot|duckduckbot|baiduspider|yandex/iu;
|
|
18186
|
+
var ipv4ToInt = (ipAddress) => ipAddress.split(".").reduce((acc, octet) => acc * IPV4_OCTET_SPACE + Number(octet), 0) >>> 0;
|
|
18187
|
+
var matchCidrV4 = (ipAddress, cidr2) => {
|
|
18188
|
+
const [range, bitsRaw] = cidr2.split("/");
|
|
18189
|
+
const bits = Number(bitsRaw);
|
|
18190
|
+
if (range === undefined || !Number.isInteger(bits) || !ipAddress.includes(".")) {
|
|
18191
|
+
return false;
|
|
18192
|
+
}
|
|
18193
|
+
const mask = bits === 0 ? 0 : FULL_MASK << IPV4_BITS - bits >>> 0;
|
|
18194
|
+
return (ipv4ToInt(ipAddress) & mask) === (ipv4ToInt(range) & mask);
|
|
18195
|
+
};
|
|
18196
|
+
var ipInList = (ipAddress, list) => list.some((entry) => entry.includes("/") ? matchCidrV4(ipAddress, entry) : entry === ipAddress);
|
|
18197
|
+
var mostSevere = (reasons) => reasons.reduce((worst, reason) => ACTION_SEVERITY[reason.action] > ACTION_SEVERITY[worst] ? reason.action : worst, "allow");
|
|
18198
|
+
var assessAbuse = async (config, context) => {
|
|
18199
|
+
const ipAddress = context.ip;
|
|
18200
|
+
if (ipAddress !== undefined && config.ipAllow !== undefined && config.ipAllow.length > 0 && ipInList(ipAddress, config.ipAllow)) {
|
|
18201
|
+
return { action: "allow", reasons: [] };
|
|
18202
|
+
}
|
|
18203
|
+
const reasons = [];
|
|
18204
|
+
if (ipAddress !== undefined && config.ipDeny !== undefined && ipInList(ipAddress, config.ipDeny)) {
|
|
18205
|
+
reasons.push({ action: "deny", signal: "blocked_ip" });
|
|
18206
|
+
}
|
|
18207
|
+
if (ipAddress !== undefined && config.ipAllow !== undefined && config.ipAllow.length > 0 && !ipInList(ipAddress, config.ipAllow)) {
|
|
18208
|
+
reasons.push({ action: "deny", signal: "not_allowlisted" });
|
|
18209
|
+
}
|
|
18210
|
+
const captchaPassed = config.verifyCaptcha === undefined || await config.verifyCaptcha(context.captchaToken, context);
|
|
18211
|
+
if (!captchaPassed) {
|
|
18212
|
+
reasons.push({
|
|
18213
|
+
action: config.captchaAction ?? "deny",
|
|
18214
|
+
signal: "captcha_failed"
|
|
18215
|
+
});
|
|
18216
|
+
}
|
|
18217
|
+
const botClass = config.classifyBot === undefined ? "human" : await config.classifyBot(context);
|
|
18218
|
+
if (botClass !== "human") {
|
|
18219
|
+
reasons.push({ action: config.botAction ?? "deny", signal: "bot" });
|
|
18220
|
+
}
|
|
18221
|
+
return { action: mostSevere(reasons), reasons };
|
|
18222
|
+
};
|
|
18223
|
+
var createAbuseGuard = (config) => ({
|
|
18224
|
+
assess: (context) => assessAbuse(config, context)
|
|
18225
|
+
});
|
|
18226
|
+
var defaultBotClassifier = (context) => {
|
|
18227
|
+
const userAgent = context.userAgent ?? "";
|
|
18228
|
+
if (userAgent.trim() === "")
|
|
18229
|
+
return "bot";
|
|
18230
|
+
if (CRAWLER_PATTERN.test(userAgent))
|
|
18231
|
+
return "crawler";
|
|
18232
|
+
if (BOT_PATTERN.test(userAgent))
|
|
18233
|
+
return "bot";
|
|
18234
|
+
return "human";
|
|
18235
|
+
};
|
|
17911
18236
|
// src/compliance/redaction.ts
|
|
17912
18237
|
var createAuditRedactor = ({
|
|
17913
18238
|
dropFields = [],
|
|
@@ -18402,7 +18727,7 @@ var DEFAULT_VELOCITY_WINDOW_MS = MILLISECONDS_IN_A_MINUTE * DEFAULT_VELOCITY_WIN
|
|
|
18402
18727
|
var EARTH_RADIUS_KM = 6371;
|
|
18403
18728
|
var DEGREES_PER_HALF_TURN = 180;
|
|
18404
18729
|
var HALF = 2;
|
|
18405
|
-
var
|
|
18730
|
+
var ACTION_SEVERITY2 = {
|
|
18406
18731
|
allow: 0,
|
|
18407
18732
|
deny: 2,
|
|
18408
18733
|
step_up: 1
|
|
@@ -18425,7 +18750,7 @@ var haversineKm = (start, end) => {
|
|
|
18425
18750
|
const factor = sinLat * sinLat + Math.cos(toRadians(start.latitude)) * Math.cos(toRadians(end.latitude)) * sinLon * sinLon;
|
|
18426
18751
|
return HALF * EARTH_RADIUS_KM * Math.asin(Math.sqrt(factor));
|
|
18427
18752
|
};
|
|
18428
|
-
var
|
|
18753
|
+
var mostSevere2 = (reasons) => reasons.reduce((worst, reason) => ACTION_SEVERITY2[reason.action] > ACTION_SEVERITY2[worst] ? reason.action : worst, "allow");
|
|
18429
18754
|
var assessRisk = async (config, context) => {
|
|
18430
18755
|
const {
|
|
18431
18756
|
historyLimit = DEFAULT_HISTORY_LIMIT,
|
|
@@ -18469,7 +18794,7 @@ var assessRisk = async (config, context) => {
|
|
|
18469
18794
|
if (recentCount >= velocityMaxAttempts) {
|
|
18470
18795
|
reasons.push({ action: actions.velocity, signal: "velocity" });
|
|
18471
18796
|
}
|
|
18472
|
-
return { action:
|
|
18797
|
+
return { action: mostSevere2(reasons), reasons };
|
|
18473
18798
|
};
|
|
18474
18799
|
var createRiskEngine = (config) => ({
|
|
18475
18800
|
assessRisk: (context) => assessRisk(config, context),
|
|
@@ -19420,12 +19745,14 @@ export {
|
|
|
19420
19745
|
verifyWebhookSignature,
|
|
19421
19746
|
verifyTotp,
|
|
19422
19747
|
verifyPassword,
|
|
19748
|
+
verifyAuditChain,
|
|
19423
19749
|
verifyApiKey,
|
|
19424
19750
|
verifyAccessToken,
|
|
19425
19751
|
validateSession,
|
|
19426
19752
|
userSessionIdTypebox,
|
|
19427
19753
|
trustDevice,
|
|
19428
19754
|
stepUpPlugin,
|
|
19755
|
+
startImpersonation,
|
|
19429
19756
|
ssoDiscoveryRoute,
|
|
19430
19757
|
ssoConnectionsTable,
|
|
19431
19758
|
signWebhook,
|
|
@@ -19438,6 +19765,7 @@ export {
|
|
|
19438
19765
|
scimTokensTable,
|
|
19439
19766
|
scimRoutes,
|
|
19440
19767
|
samlSsoRoutes,
|
|
19768
|
+
rotateMfaEncryptionKey,
|
|
19441
19769
|
rolesTable,
|
|
19442
19770
|
roleRoutes,
|
|
19443
19771
|
revokeUserSessions,
|
|
@@ -19486,11 +19814,13 @@ export {
|
|
|
19486
19814
|
isPKCEProviderOption,
|
|
19487
19815
|
isOIDCProviderOption,
|
|
19488
19816
|
isMfaEnrolled,
|
|
19817
|
+
isImpersonating,
|
|
19489
19818
|
isAuthIntent,
|
|
19490
19819
|
inviteToOrganization,
|
|
19491
19820
|
instantiateUserSession,
|
|
19492
19821
|
hashToken,
|
|
19493
19822
|
hashPassword,
|
|
19823
|
+
hashAuditEvent,
|
|
19494
19824
|
hasScopes,
|
|
19495
19825
|
hasOrganizationScope,
|
|
19496
19826
|
getUserSessionId,
|
|
@@ -19503,12 +19833,14 @@ export {
|
|
|
19503
19833
|
extractPropFromIdentity,
|
|
19504
19834
|
exchangeClientCredentials,
|
|
19505
19835
|
evaluatePassword,
|
|
19836
|
+
endImpersonation,
|
|
19506
19837
|
encryptTotpSecret,
|
|
19507
19838
|
encryptSecret,
|
|
19508
19839
|
defineProvidersConfiguration,
|
|
19509
19840
|
defineAuthSettings,
|
|
19510
19841
|
defineAuthHtmxConfig,
|
|
19511
19842
|
defineAuthConfig,
|
|
19843
|
+
defaultBotClassifier,
|
|
19512
19844
|
decryptTotpSecret,
|
|
19513
19845
|
decryptSecret,
|
|
19514
19846
|
decodeJWT,
|
|
@@ -19522,6 +19854,8 @@ export {
|
|
|
19522
19854
|
credentialResetTokensTable,
|
|
19523
19855
|
createWebhookDispatcher,
|
|
19524
19856
|
createTotpKeyUri,
|
|
19857
|
+
createTamperEvidentSink,
|
|
19858
|
+
createSiemLogStream,
|
|
19525
19859
|
createSetupSession,
|
|
19526
19860
|
createSecretCipher,
|
|
19527
19861
|
createScimToken,
|
|
@@ -19593,6 +19927,7 @@ export {
|
|
|
19593
19927
|
createAuditEmitter,
|
|
19594
19928
|
createApiKey,
|
|
19595
19929
|
createApiClient,
|
|
19930
|
+
createAbuseGuard,
|
|
19596
19931
|
consumeBackupCode,
|
|
19597
19932
|
constantTimeEqual,
|
|
19598
19933
|
complianceRoutes,
|
|
@@ -19605,6 +19940,7 @@ export {
|
|
|
19605
19940
|
auth,
|
|
19606
19941
|
auditEventsTable,
|
|
19607
19942
|
assessRisk,
|
|
19943
|
+
assessAbuse,
|
|
19608
19944
|
apiKeysTable,
|
|
19609
19945
|
apiKeysRoutes,
|
|
19610
19946
|
apiClientsTable,
|
|
@@ -19639,5 +19975,5 @@ export {
|
|
|
19639
19975
|
AuthIdentityConflictError
|
|
19640
19976
|
};
|
|
19641
19977
|
|
|
19642
|
-
//# debugId=
|
|
19978
|
+
//# debugId=5C9E1C93D2A5368D64756E2164756E21
|
|
19643
19979
|
//# sourceMappingURL=index.js.map
|