@gatwech/auth 1.0.0-beta.18 → 1.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -14
- package/dist/{access-C6O_osEz.mjs → access-CrwEFPHE.mjs} +1 -1
- package/dist/{access-DT1f6l5k.mjs → access-bhklJd7m.mjs} +1 -1
- package/dist/{adapter-factory-BZ2Qiqpn.mjs → adapter-factory-xbLUJt85.mjs} +2 -2
- package/dist/adapters/drizzle-adapter/index.mjs +6 -5
- package/dist/adapters/index.d.mts +5 -5
- package/dist/adapters/index.mjs +6 -5
- package/dist/adapters/kysely-adapter/index.d.mts +1 -1
- package/dist/adapters/kysely-adapter/index.mjs +8 -7
- package/dist/adapters/memory-adapter/index.d.mts +1 -1
- package/dist/adapters/memory-adapter/index.mjs +6 -5
- package/dist/adapters/mongodb-adapter/index.mjs +6 -5
- package/dist/adapters/prisma-adapter/index.mjs +6 -5
- package/dist/adapters/test.mjs +4 -3
- package/dist/{admin-Bcq53Nwj.mjs → admin-BzUJL6tr.mjs} +6 -6
- package/dist/{anonymous-CRt1imBW.mjs → anonymous-BjlTk4gl.mjs} +5 -5
- package/dist/api/index.d.mts +5 -5
- package/dist/api/index.mjs +12 -11
- package/dist/{api-0pR_I-Nx.mjs → api-Dk8Yj_J5.mjs} +15 -5
- package/dist/audit-log-DKsUWR8O.mjs +772 -0
- package/dist/{auth-CBUoBX6o.mjs → auth-vfCyIIeV.mjs} +8 -8
- package/dist/{bearer-Djf_awfo.mjs → bearer-DvQLM00l.mjs} +2 -2
- package/dist/client/index.d.mts +19 -19
- package/dist/client/index.mjs +4 -4
- package/dist/client/plugins/index.d.mts +42 -34
- package/dist/client/plugins/index.mjs +16 -12
- package/dist/client/react/index.d.mts +5 -5
- package/dist/client/react/index.mjs +2 -2
- package/dist/client-4QEb0rUr.mjs +95 -0
- package/dist/client-C_qmyez2.d.mts +75 -0
- package/dist/client-DzOqXJUm.mjs +15 -0
- package/dist/client-F67q-Lbi.d.mts +14 -0
- package/dist/{client-aExCkfwm.mjs → client-xga7yFIX.mjs} +2 -2
- package/dist/cookies/index.d.mts +6 -6
- package/dist/cookies/index.mjs +2 -2
- package/dist/{cookies-cJObLwn1.mjs → cookies-BuvlgcA-.mjs} +1 -1
- package/dist/crypto/index.mjs +2 -1
- package/dist/{crypto-Bb42eMrH.mjs → crypto-CCyta8xj.mjs} +1 -6
- package/dist/{custom-session-L3t7ecsd.mjs → custom-session-DN_-pfi_.mjs} +2 -2
- package/dist/db/index.d.mts +5 -5
- package/dist/db/index.mjs +9 -8
- package/dist/{db-BrstmDhc.mjs → db-7vHnL1XA.mjs} +15 -9
- package/dist/{device-authorization-V4s3h4Mj.mjs → device-authorization-BKnNhlsz.mjs} +4 -4
- package/dist/{dialect-CeddeGHf.mjs → dialect-HsWUfpYY.mjs} +2 -2
- package/dist/{email-otp-BtDHSw14.mjs → email-otp-Dw0DypjQ.mjs} +7 -6
- package/dist/{generic-oauth-CL5X27ZI.mjs → generic-oauth-DFj27JY8.mjs} +4 -4
- package/dist/{has-permission-BpAr7uCn.mjs → has-permission-Bvg8de8g.mjs} +1 -1
- package/dist/{haveibeenpwned-cMpJvmzI.mjs → haveibeenpwned-CzNda7Tn.mjs} +28 -15
- package/dist/{index-BYMI9r_d.d.mts → index-4I1YhYhk.d.mts} +4 -4
- package/dist/{index-Cxi7TbLE.d.mts → index-BNhJglHC.d.mts} +122 -122
- package/dist/{index-DlaMq1nb.d.mts → index-BX7rl07Z.d.mts} +14 -14
- package/dist/{client-BOX93C3d.d.mts → index-Bml-PCqP.d.mts} +52 -52
- package/dist/{index-zjjNBRY2.d.mts → index-BsSKcKgO.d.mts} +6 -6
- package/dist/{index-Buhn3hkR.d.mts → index-CBiGsvDd.d.mts} +8 -8
- package/dist/{index-D_k662QG.d.mts → index-CC920PPp.d.mts} +14 -14
- package/dist/{index-B3n8e37d.d.mts → index-CNd5jqLl.d.mts} +7 -7
- package/dist/{index-BqhKCbPH.d.mts → index-CP8x1TFq.d.mts} +6 -6
- package/dist/{index-DKNNV1kb.d.mts → index-CofuaNMl.d.mts} +8 -8
- package/dist/{index-DR0xXno9.d.mts → index-CsxtcAO0.d.mts} +1 -1
- package/dist/{index-C7fQuk-V.d.mts → index-CxlWnL9c.d.mts} +1 -1
- package/dist/{index-CuFzhoS6.d.mts → index-CzpdkTdv.d.mts} +10 -10
- package/dist/index-D57DuhZR.d.mts +3769 -0
- package/dist/{index-C-u1h_WX.d.mts → index-D6ku0rUC.d.mts} +5 -5
- package/dist/{client-CNY0tn9g.d.mts → index-DFSRYWFI.d.mts} +21 -21
- package/dist/{index-BBm8nQ9Q.d.mts → index-DGeoJ4gu.d.mts} +2 -2
- package/dist/{index-G6V0Z_Mn.d.mts → index-DTkcY7xH.d.mts} +10 -10
- package/dist/{index-65G5U5DB-DSdjzreF.d.mts → index-Df0KhQRf-BEa5tdwJ.d.mts} +3 -2
- package/dist/{index-34ONrP8l.d.mts → index-Dnd8S4S4.d.mts} +11 -11
- package/dist/{index-B-XKqFvY.d.mts → index-DwPAddfE.d.mts} +11 -4
- package/dist/{index-DoXZYQpq.d.mts → index-DyXi7S2M.d.mts} +10 -10
- package/dist/{index-BHoQlf7a.d.mts → index-KhbNmMmG.d.mts} +178 -178
- package/dist/{index-z0KSvE5i.d.mts → index-QmocqGM-.d.mts} +1 -1
- package/dist/{admin-DtfO1PLg.d.mts → index-XfE0WjTN.d.mts} +36 -36
- package/dist/{index-DaBcikv9.d.mts → index-sb-UFLuo.d.mts} +2 -2
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +16 -15
- package/dist/integrations/next-js.d.mts +4 -4
- package/dist/integrations/next-js.mjs +2 -2
- package/dist/integrations/node.d.mts +5 -5
- package/dist/integrations/react-start.d.mts +4 -4
- package/dist/integrations/react-start.mjs +2 -2
- package/dist/{jwt-lHV4QHpD.mjs → jwt-DBN2mu0r.mjs} +4 -4
- package/dist/{kysely-adapter-DpzYO8Yf.mjs → kysely-adapter-Bkm1liPW.mjs} +1 -1
- package/dist/{magic-link-Bk-ueorR.mjs → magic-link-D9Em1aOR.mjs} +3 -3
- package/dist/{multi-session-DRrLKrg9.mjs → multi-session-Dno_8DxC.mjs} +5 -5
- package/dist/{oauth-proxy-D1sWh9B_.mjs → oauth-proxy-DW-zt8wv.mjs} +2 -2
- package/dist/oauth2/index.d.mts +5 -5
- package/dist/oauth2/index.mjs +12 -11
- package/dist/{one-tap-DMswkTEc.mjs → one-tap-CodeyQb8.mjs} +2 -2
- package/dist/{organization-BArHO_5v.mjs → organization-CkTKs7bx.mjs} +9 -9
- package/dist/{passkey-CcD4CnhM.mjs → passkey-JNQTz5a0.mjs} +5 -5
- package/dist/{phone-number-rHDIlh-2.mjs → phone-number-Dqz8aeyF.mjs} +4 -4
- package/dist/plugins/access/index.d.mts +2 -2
- package/dist/plugins/access/index.mjs +1 -1
- package/dist/plugins/admin/access/index.d.mts +2 -2
- package/dist/plugins/admin/access/index.mjs +2 -2
- package/dist/plugins/admin/index.d.mts +7 -8
- package/dist/plugins/admin/index.mjs +17 -16
- package/dist/plugins/anonymous/index.d.mts +6 -6
- package/dist/plugins/anonymous/index.mjs +13 -12
- package/dist/plugins/audit-log/client.d.mts +30 -0
- package/dist/plugins/audit-log/client.mjs +3 -0
- package/dist/plugins/audit-log/index.d.mts +29 -0
- package/dist/plugins/audit-log/index.mjs +20 -0
- package/dist/plugins/bearer/index.d.mts +1 -1
- package/dist/plugins/bearer/index.mjs +3 -3
- package/dist/plugins/captcha/index.d.mts +1 -1
- package/dist/plugins/captcha/index.mjs +1 -1
- package/dist/plugins/custom-session/index.d.mts +6 -6
- package/dist/plugins/custom-session/index.mjs +14 -13
- package/dist/plugins/device-authorization/index.d.mts +3 -3
- package/dist/plugins/device-authorization/index.mjs +13 -12
- package/dist/plugins/email-otp/index.d.mts +2 -2
- package/dist/plugins/email-otp/index.mjs +14 -13
- package/dist/plugins/generic-oauth/index.d.mts +6 -6
- package/dist/plugins/generic-oauth/index.mjs +13 -12
- package/dist/plugins/haveibeenpwned/index.d.mts +2 -2
- package/dist/plugins/haveibeenpwned/index.mjs +14 -13
- package/dist/plugins/index.d.mts +29 -139
- package/dist/plugins/index.mjs +47 -42
- package/dist/plugins/jwt/index.d.mts +6 -6
- package/dist/plugins/jwt/index.mjs +13 -12
- package/dist/plugins/magic-link/index.d.mts +1 -1
- package/dist/plugins/magic-link/index.mjs +13 -12
- package/dist/plugins/multi-session/index.d.mts +2 -2
- package/dist/plugins/multi-session/index.mjs +13 -12
- package/dist/plugins/oauth-proxy/index.d.mts +1 -1
- package/dist/plugins/oauth-proxy/index.mjs +13 -12
- package/dist/plugins/one-tap/index.d.mts +1 -1
- package/dist/plugins/one-tap/index.mjs +13 -12
- package/dist/plugins/one-time-token/index.d.mts +6 -6
- package/dist/plugins/one-time-token/index.mjs +46 -41
- package/dist/plugins/organization/access/index.d.mts +3 -3
- package/dist/plugins/organization/access/index.mjs +2 -2
- package/dist/plugins/organization/index.d.mts +8 -8
- package/dist/plugins/organization/index.mjs +17 -16
- package/dist/plugins/passkey/index.d.mts +6 -6
- package/dist/plugins/passkey/index.mjs +13 -12
- package/dist/plugins/phone-number/index.d.mts +6 -6
- package/dist/plugins/phone-number/index.mjs +13 -12
- package/dist/plugins/sentinel/client.d.mts +30 -0
- package/dist/plugins/sentinel/client.mjs +5 -0
- package/dist/plugins/sentinel/index.d.mts +29 -0
- package/dist/plugins/sentinel/index.mjs +22 -0
- package/dist/plugins/two-factor/index.d.mts +6 -6
- package/dist/plugins/two-factor/index.mjs +15 -14
- package/dist/plugins/username/index.d.mts +3 -3
- package/dist/plugins/username/index.mjs +13 -12
- package/dist/{plugins-hFZT3hmK.d.mts → plugins-Dy_FrD4Y.d.mts} +1 -1
- package/dist/{plugins-3jfNste3.mjs → plugins-yF8CFUDR.mjs} +11 -8
- package/dist/pow-OK01yPG3.mjs +61 -0
- package/dist/{proxy-CL9PZXJA.mjs → proxy-C_KXfOhk.mjs} +1 -1
- package/dist/random-32E3cU5o.mjs +7 -0
- package/dist/registry-CqG_3Qnx.mjs +194 -0
- package/dist/sentinel-DiPqydKG.mjs +1426 -0
- package/dist/{session-Dgp133Yt.mjs → session-BB3oqApE.mjs} +2 -2
- package/dist/src/adapters/memory-adapter/memory-adapter.d.ts +1 -1
- package/dist/src/adapters/prisma-adapter/test/auth.d.ts +1 -1
- package/dist/src/adapters/tests/auth-flow.d.ts +3 -3
- package/dist/src/adapters/tests/normal.d.ts +1 -1
- package/dist/src/adapters/tests/number-id.d.ts +3 -3
- package/dist/src/adapters/tests/performance.d.ts +3 -3
- package/dist/src/adapters/tests/transactions.d.ts +3 -3
- package/dist/src/api/index.d.ts +8 -8
- package/dist/src/client/plugins/index.d.ts +2 -0
- package/dist/src/plugins/admin/admin.d.ts +2 -2
- package/dist/src/plugins/api-key/index.d.ts +2 -2
- package/dist/src/plugins/audit-log/client.d.ts +10 -0
- package/dist/src/plugins/audit-log/events.d.ts +90 -0
- package/dist/src/plugins/audit-log/index.d.ts +614 -0
- package/dist/src/plugins/audit-log/registry.d.ts +13 -0
- package/dist/src/plugins/audit-log/remote.d.ts +34 -0
- package/dist/src/plugins/audit-log/schema.d.ts +50 -0
- package/dist/src/plugins/audit-log/types.d.ts +117 -0
- package/dist/src/plugins/email-otp/index.d.ts +1 -1
- package/dist/src/plugins/haveibeenpwned/index.d.ts +6 -0
- package/dist/src/plugins/index.d.ts +2 -0
- package/dist/src/plugins/multi-session/index.d.ts +8 -8
- package/dist/src/plugins/organization/organization.d.ts +5 -5
- package/dist/src/plugins/organization/routes/crud-access-control.d.ts +8 -8
- package/dist/src/plugins/organization/routes/crud-invites.d.ts +46 -46
- package/dist/src/plugins/organization/routes/crud-members.d.ts +40 -40
- package/dist/src/plugins/organization/routes/crud-org.d.ts +36 -36
- package/dist/src/plugins/organization/routes/crud-team.d.ts +50 -50
- package/dist/src/plugins/sentinel/actions.d.ts +64 -0
- package/dist/src/plugins/sentinel/client.d.ts +67 -0
- package/dist/src/plugins/sentinel/email.d.ts +28 -0
- package/dist/src/plugins/sentinel/geo.d.ts +22 -0
- package/dist/src/plugins/sentinel/index.d.ts +326 -0
- package/dist/src/plugins/sentinel/intel.d.ts +45 -0
- package/dist/src/plugins/sentinel/pow.d.ts +21 -0
- package/dist/src/plugins/sentinel/schema.d.ts +110 -0
- package/dist/src/plugins/sentinel/store.d.ts +37 -0
- package/dist/src/plugins/sentinel/types.d.ts +412 -0
- package/dist/src/test-utils/memory-secondary-storage.d.ts +16 -0
- package/dist/src/test-utils/password.d.ts +12 -0
- package/dist/src/test-utils/test-instance.d.ts +25 -25
- package/dist/test-utils/index.d.mts +293 -293
- package/dist/test-utils/index.mjs +53 -48
- package/dist/{two-factor-ClUXxom2.mjs → two-factor-CDVjxx4f.mjs} +6 -5
- package/dist/types/index.d.mts +5 -5
- package/dist/types/index.mjs +1 -1
- package/dist/{username-DiGklOGl.mjs → username-BHNvcvsY.mjs} +3 -3
- package/dist/{utils-SOMB4Px5.mjs → utils-B03WRaGi.mjs} +2 -1
- package/package.json +36 -3
- package/dist/index-CM-CFWwr.d.mts +0 -1919
- package/dist/index-DB7t8whN.d.mts +0 -1
- /package/dist/{access-BO2g7QVe.mjs → access-BM6jSA5j.mjs} +0 -0
- /package/dist/{bun-sqlite-dialect-CeNvMaZz.mjs → bun-sqlite-dialect-CDGusFqF.mjs} +0 -0
- /package/dist/{captcha-BqaMxlKC.mjs → captcha-DGP9lCRe.mjs} +0 -0
- /package/dist/{client-CygJhqeB.mjs → client-BuveH3MN.mjs} +0 -0
- /package/dist/{client-WiN43tAS.mjs → client-cBiytuOL.mjs} +0 -0
- /package/dist/{date-Cx2yCiC4.mjs → date-CMr04reD.mjs} +0 -0
- /package/dist/{esm-C-QicDNz.mjs → esm-BbvI87Z2.mjs} +0 -0
- /package/dist/{get-tables-BQP7YW9c.mjs → get-tables-CKvSyDST.mjs} +0 -0
- /package/dist/{helper-tmxW3BHj.d.mts → helper-BgOIGHsu.d.mts} +0 -0
- /package/dist/{misc-beA1w3XV.mjs → misc-fKH4d5XQ.mjs} +0 -0
- /package/dist/{node-sqlite-dialect-BcKxBQnm.mjs → node-sqlite-dialect-CIOjcg31.mjs} +0 -0
- /package/dist/{parser-DcrIEuXE.mjs → parser-D7EkhAjZ.mjs} +0 -0
- /package/dist/{password-sVC4nePO.mjs → password-5CdcAVaH.mjs} +0 -0
- /package/dist/{permission-DLYUF3cW.mjs → permission-CDDWgA_O.mjs} +0 -0
- /package/dist/{plugin-helper-4L-lzsjZ.mjs → plugin-helper-RJJBTOba.mjs} +0 -0
- /package/dist/{promise-8rrEHRXd.mjs → promise-D7Oo6_cY.mjs} +0 -0
- /package/dist/{types-Bjn15Uy6.mjs → types-DCPpFKUB.mjs} +0 -0
- /package/dist/{types-B1OL7hyf.d.mts → types-DNQ9XQrB.d.mts} +0 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
import { t as getIp } from "./get-request-ip-DSjrEpGo.mjs";
|
|
2
|
+
import { t as generateRandomString } from "./random-32E3cU5o.mjs";
|
|
3
|
+
import { n as mergeSchema } from "./date-CMr04reD.mjs";
|
|
4
|
+
import { r as getSessionFromCtx, u as sessionMiddleware } from "./session-BB3oqApE.mjs";
|
|
5
|
+
import { a as CREDENTIAL_SIGN_IN_PATHS, c as SIGN_IN_PATHS, i as AUDIT_EVENT_TYPES, l as VERIFICATION_PATHS, n as registerAuditRecorder, o as PATH_EVENTS, r as ALL_EVENT_TYPES } from "./registry-CqG_3Qnx.mjs";
|
|
6
|
+
import { env } from "@gatwech/core/env";
|
|
7
|
+
import { defineErrorCodes } from "@gatwech/core/utils";
|
|
8
|
+
import { APIError } from "better-call";
|
|
9
|
+
import { createAuthEndpoint, createAuthMiddleware } from "@gatwech/core/api";
|
|
10
|
+
import * as z$1 from "zod";
|
|
11
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
12
|
+
|
|
13
|
+
//#region src/plugins/audit-log/remote.ts
|
|
14
|
+
/**
|
|
15
|
+
* Batches events and sends them to the hosted backend. Sending is never
|
|
16
|
+
* awaited by the auth flow, and failures are logged and dropped.
|
|
17
|
+
*/
|
|
18
|
+
function createRemoteSink(options) {
|
|
19
|
+
if (options === false) return {
|
|
20
|
+
sink: null,
|
|
21
|
+
misconfigured: false
|
|
22
|
+
};
|
|
23
|
+
const apiKey = options?.apiKey ?? env.PAYVO_INFRA_API_KEY;
|
|
24
|
+
const apiUrl = options?.apiUrl ?? env.PAYVO_INFRA_API_URL;
|
|
25
|
+
if (!apiKey || !apiUrl) return {
|
|
26
|
+
sink: null,
|
|
27
|
+
misconfigured: Boolean(options)
|
|
28
|
+
};
|
|
29
|
+
const baseURL = apiUrl.replace(/\/+$/, "");
|
|
30
|
+
const timeout = options?.timeout ?? 3e3;
|
|
31
|
+
const maxSize = options?.batch?.maxSize ?? 25;
|
|
32
|
+
const flushIntervalMs = options?.batch?.flushIntervalMs ?? 2e3;
|
|
33
|
+
const headers = {
|
|
34
|
+
...options?.headers,
|
|
35
|
+
authorization: `Bearer ${apiKey}`
|
|
36
|
+
};
|
|
37
|
+
let buffer = [];
|
|
38
|
+
let timer = null;
|
|
39
|
+
async function send(events, logger$1) {
|
|
40
|
+
const { error } = await betterFetch(`${baseURL}/v1/events`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers,
|
|
43
|
+
body: { events },
|
|
44
|
+
timeout
|
|
45
|
+
}).catch((e) => ({ error: e }));
|
|
46
|
+
if (error) logger$1?.error("[audit-log] Failed to send audit events", error);
|
|
47
|
+
}
|
|
48
|
+
async function flush(logger$1) {
|
|
49
|
+
if (timer) {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
timer = null;
|
|
52
|
+
}
|
|
53
|
+
if (!buffer.length) return;
|
|
54
|
+
const events = buffer;
|
|
55
|
+
buffer = [];
|
|
56
|
+
await send(events, logger$1);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
sink: {
|
|
60
|
+
push(event, logger$1) {
|
|
61
|
+
buffer.push(event);
|
|
62
|
+
if (buffer.length >= maxSize) {
|
|
63
|
+
flush(logger$1);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!timer) {
|
|
67
|
+
timer = setTimeout(() => void flush(logger$1), flushIntervalMs);
|
|
68
|
+
timer.unref?.();
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
flush,
|
|
72
|
+
async query({ userIds, organizationIds,...query }) {
|
|
73
|
+
const params = new URLSearchParams();
|
|
74
|
+
for (const [key, value] of Object.entries(query)) if (value !== void 0) params.set(key, String(value));
|
|
75
|
+
for (const id of userIds ?? []) params.append("userId", id);
|
|
76
|
+
for (const id of organizationIds ?? []) params.append("organizationId", id);
|
|
77
|
+
const { data, error } = await betterFetch(`${baseURL}/v1/events?${params.toString()}`, {
|
|
78
|
+
headers,
|
|
79
|
+
timeout
|
|
80
|
+
});
|
|
81
|
+
if (error || !data) throw new Error(`Failed to query audit events: ${error?.status ?? "no response"}`);
|
|
82
|
+
return {
|
|
83
|
+
...data,
|
|
84
|
+
events: data.events.map((event) => ({
|
|
85
|
+
...event,
|
|
86
|
+
createdAt: new Date(event.createdAt)
|
|
87
|
+
}))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
misconfigured: false
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/plugins/audit-log/schema.ts
|
|
97
|
+
const schema = { auditLog: { fields: {
|
|
98
|
+
eventType: {
|
|
99
|
+
type: "string",
|
|
100
|
+
required: true
|
|
101
|
+
},
|
|
102
|
+
userId: {
|
|
103
|
+
type: "string",
|
|
104
|
+
required: false
|
|
105
|
+
},
|
|
106
|
+
identifier: {
|
|
107
|
+
type: "string",
|
|
108
|
+
required: false
|
|
109
|
+
},
|
|
110
|
+
organizationId: {
|
|
111
|
+
type: "string",
|
|
112
|
+
required: false
|
|
113
|
+
},
|
|
114
|
+
status: {
|
|
115
|
+
type: "string",
|
|
116
|
+
required: true
|
|
117
|
+
},
|
|
118
|
+
path: {
|
|
119
|
+
type: "string",
|
|
120
|
+
required: false
|
|
121
|
+
},
|
|
122
|
+
ipAddress: {
|
|
123
|
+
type: "string",
|
|
124
|
+
required: false
|
|
125
|
+
},
|
|
126
|
+
userAgent: {
|
|
127
|
+
type: "string",
|
|
128
|
+
required: false
|
|
129
|
+
},
|
|
130
|
+
visitorId: {
|
|
131
|
+
type: "string",
|
|
132
|
+
required: false
|
|
133
|
+
},
|
|
134
|
+
eventData: {
|
|
135
|
+
type: "json",
|
|
136
|
+
required: false
|
|
137
|
+
},
|
|
138
|
+
createdAt: {
|
|
139
|
+
type: "date",
|
|
140
|
+
required: true
|
|
141
|
+
}
|
|
142
|
+
} } };
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/plugins/audit-log/index.ts
|
|
146
|
+
const AUDIT_LOG_ERROR_CODES = defineErrorCodes({
|
|
147
|
+
AUDIT_LOG_ACCESS_DENIED: "You don't have access to these audit logs",
|
|
148
|
+
AUDIT_LOG_UNAVAILABLE: "Audit logs are temporarily unavailable"
|
|
149
|
+
});
|
|
150
|
+
/** User fields that change as a side effect and aren't profile edits */
|
|
151
|
+
const INTERNAL_USER_FIELDS = new Set([
|
|
152
|
+
"updatedAt",
|
|
153
|
+
"emailVerified",
|
|
154
|
+
"image",
|
|
155
|
+
"banned",
|
|
156
|
+
"banReason",
|
|
157
|
+
"banExpires",
|
|
158
|
+
"lastLoginMethod",
|
|
159
|
+
"twoFactorEnabled",
|
|
160
|
+
"role"
|
|
161
|
+
]);
|
|
162
|
+
const isDate = (value) => !Number.isNaN(new Date(value).getTime());
|
|
163
|
+
const auditLogQuery = {
|
|
164
|
+
limit: z$1.string().meta({ description: "The number of events to return. Max 100, defaults to 50" }).or(z$1.number()).optional(),
|
|
165
|
+
offset: z$1.string().meta({ description: "The offset to start from" }).or(z$1.number()).optional(),
|
|
166
|
+
eventType: z$1.string().meta({ description: "Filter by event type. Eg: \"user_signed_in\"" }).optional(),
|
|
167
|
+
identifier: z$1.string().meta({ description: "Filter by the actor's identifier. Eg: \"user@example.com\"" }).optional(),
|
|
168
|
+
organizationId: z$1.string().meta({ description: "Filter by organization" }).optional(),
|
|
169
|
+
userId: z$1.string().meta({ description: "Filter by user" }).optional(),
|
|
170
|
+
startDate: z$1.string().refine(isDate, "Invalid date").meta({ description: "Only return events created at or after this date. Eg: \"2026-01-01\"" }).optional(),
|
|
171
|
+
endDate: z$1.string().refine(isDate, "Invalid date").meta({ description: "Only return events created at or before this date. Eg: \"2026-01-31\"" }).optional()
|
|
172
|
+
};
|
|
173
|
+
function toPagination(query) {
|
|
174
|
+
const limit = Number(query.limit) || 50;
|
|
175
|
+
return {
|
|
176
|
+
limit: Math.min(Math.max(Math.trunc(limit), 1), 100),
|
|
177
|
+
offset: Math.max(Math.trunc(Number(query.offset) || 0), 0)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const auditLog = (options) => {
|
|
181
|
+
const opts = {
|
|
182
|
+
...options,
|
|
183
|
+
database: options?.database ?? true,
|
|
184
|
+
trackFailedSignIns: options?.trackFailedSignIns ?? true,
|
|
185
|
+
adminRoles: options?.adminRoles ?? ["admin"]
|
|
186
|
+
};
|
|
187
|
+
const excluded = new Set(opts.exclude ?? []);
|
|
188
|
+
const { sink: remote, misconfigured } = createRemoteSink(opts.remote);
|
|
189
|
+
const mergedSchema = mergeSchema(schema, opts.schema);
|
|
190
|
+
let authContext;
|
|
191
|
+
/** Pending update payloads, so `update.after` knows which fields changed */
|
|
192
|
+
const pendingUserUpdates = /* @__PURE__ */ new WeakMap();
|
|
193
|
+
const noContextKey = {};
|
|
194
|
+
/** Users created in a request, so their first account isn't a link */
|
|
195
|
+
const createdUsers = /* @__PURE__ */ new WeakMap();
|
|
196
|
+
/**
|
|
197
|
+
* The signed-in actor of an audited request, keyed by the request headers
|
|
198
|
+
* both hooks receive. Read before the endpoint runs because some endpoints
|
|
199
|
+
* delete the session, and sessions are deleted without database hooks.
|
|
200
|
+
*/
|
|
201
|
+
const actors = /* @__PURE__ */ new WeakMap();
|
|
202
|
+
/** The user an admin endpoint acted on, when the body doesn't name them */
|
|
203
|
+
const targets = /* @__PURE__ */ new WeakMap();
|
|
204
|
+
/** The organization an organization endpoint acted on */
|
|
205
|
+
async function resolveOrganizationId(auth, path, body, result, activeOrganizationId) {
|
|
206
|
+
const direct = body.organizationId ?? result.organizationId ?? result.member?.organizationId ?? result.invitation?.organizationId ?? (path === "/organization/create" || path === "/organization/update" ? result.id : void 0);
|
|
207
|
+
if (typeof direct === "string") return direct;
|
|
208
|
+
if (!path.startsWith("/organization/")) return null;
|
|
209
|
+
if (typeof body.teamId === "string") {
|
|
210
|
+
const team = await auth.adapter.findOne({
|
|
211
|
+
model: "team",
|
|
212
|
+
where: [{
|
|
213
|
+
field: "id",
|
|
214
|
+
value: body.teamId
|
|
215
|
+
}]
|
|
216
|
+
}).catch(() => null);
|
|
217
|
+
if (team) return team.organizationId;
|
|
218
|
+
}
|
|
219
|
+
return activeOrganizationId;
|
|
220
|
+
}
|
|
221
|
+
function requestMeta(ctx, auth) {
|
|
222
|
+
const headers = ctx?.headers ?? ctx?.request?.headers;
|
|
223
|
+
return {
|
|
224
|
+
path: ctx?.path ?? null,
|
|
225
|
+
ipAddress: headers ? getIp(headers, auth.options) : null,
|
|
226
|
+
userAgent: headers?.get("user-agent") ?? null,
|
|
227
|
+
visitorId: headers?.get("x-visitor-id") ?? null
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The signed-in user making a request, and the admin impersonating them,
|
|
232
|
+
* when known
|
|
233
|
+
*/
|
|
234
|
+
function actorOf(ctx) {
|
|
235
|
+
const session = (ctx?.context)?.session;
|
|
236
|
+
if (session?.user?.id) return {
|
|
237
|
+
id: session.user.id,
|
|
238
|
+
impersonatedBy: session.session?.impersonatedBy ?? null
|
|
239
|
+
};
|
|
240
|
+
const headers = ctx?.headers ?? ctx?.request?.headers;
|
|
241
|
+
const actor = headers ? actors.get(headers) : void 0;
|
|
242
|
+
return {
|
|
243
|
+
id: actor?.id ?? null,
|
|
244
|
+
impersonatedBy: actor?.impersonatedBy ?? null
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Records an event in every enabled sink. Never throws: audit logging must
|
|
249
|
+
* not break authentication.
|
|
250
|
+
*/
|
|
251
|
+
async function record(ctx, event) {
|
|
252
|
+
const auth = ctx?.context?.adapter ? ctx.context : authContext;
|
|
253
|
+
if (!auth || excluded.has(event.eventType)) return;
|
|
254
|
+
try {
|
|
255
|
+
const meta = requestMeta(ctx, auth);
|
|
256
|
+
const actor = actorOf(ctx);
|
|
257
|
+
const attribution = {
|
|
258
|
+
...actor.id && actor.id !== event.userId ? { actorId: actor.id } : {},
|
|
259
|
+
...actor.impersonatedBy ? { impersonatedBy: actor.impersonatedBy } : {}
|
|
260
|
+
};
|
|
261
|
+
const eventData = Object.keys(attribution).length ? {
|
|
262
|
+
...event.eventData,
|
|
263
|
+
...attribution
|
|
264
|
+
} : event.eventData ?? null;
|
|
265
|
+
const { id: _,...data } = {
|
|
266
|
+
id: "",
|
|
267
|
+
status: "success",
|
|
268
|
+
userId: null,
|
|
269
|
+
identifier: null,
|
|
270
|
+
organizationId: null,
|
|
271
|
+
...meta,
|
|
272
|
+
...event,
|
|
273
|
+
eventData,
|
|
274
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
275
|
+
};
|
|
276
|
+
let id = auth.generateId({ model: "auditLog" }) || "";
|
|
277
|
+
if (opts.database) {
|
|
278
|
+
const row = id ? {
|
|
279
|
+
...data,
|
|
280
|
+
id
|
|
281
|
+
} : data;
|
|
282
|
+
id = (await auth.adapter.create({
|
|
283
|
+
model: "auditLog",
|
|
284
|
+
data: row,
|
|
285
|
+
forceAllowId: Boolean(id)
|
|
286
|
+
}).catch((e) => {
|
|
287
|
+
auth.logger.error("[audit-log] Failed to write audit log", e);
|
|
288
|
+
return null;
|
|
289
|
+
}))?.id ?? id;
|
|
290
|
+
}
|
|
291
|
+
const full = {
|
|
292
|
+
...data,
|
|
293
|
+
id: id || generateRandomString(32)
|
|
294
|
+
};
|
|
295
|
+
remote?.push(full, auth.logger);
|
|
296
|
+
const onEvent = opts.onEvent;
|
|
297
|
+
if (onEvent) await Promise.resolve().then(() => onEvent(full)).catch((e) => auth.logger.error("[audit-log] onEvent handler failed", e));
|
|
298
|
+
} catch (e) {
|
|
299
|
+
auth.logger.error("[audit-log] Failed to record audit event", e);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function userEventsFromUpdate(data) {
|
|
303
|
+
const events = [];
|
|
304
|
+
if (data.emailVerified === true) events.push({ eventType: AUDIT_EVENT_TYPES.USER_EMAIL_VERIFIED });
|
|
305
|
+
if (data.banned === true) events.push({ eventType: AUDIT_EVENT_TYPES.USER_BANNED });
|
|
306
|
+
if (data.banned === false) events.push({ eventType: AUDIT_EVENT_TYPES.USER_UNBANNED });
|
|
307
|
+
if (data.email !== void 0) events.push({ eventType: AUDIT_EVENT_TYPES.USER_EMAIL_CHANGED });
|
|
308
|
+
if (data.phoneNumber !== void 0) events.push({ eventType: AUDIT_EVENT_TYPES.USER_PHONE_NUMBER_CHANGED });
|
|
309
|
+
if (data.role !== void 0) events.push({
|
|
310
|
+
eventType: AUDIT_EVENT_TYPES.USER_ROLE_UPDATED,
|
|
311
|
+
eventData: { role: data.role }
|
|
312
|
+
});
|
|
313
|
+
if (data.twoFactorEnabled === true) events.push({ eventType: AUDIT_EVENT_TYPES.TWO_FACTOR_ENABLED });
|
|
314
|
+
if (data.twoFactorEnabled === false) events.push({ eventType: AUDIT_EVENT_TYPES.TWO_FACTOR_DISABLED });
|
|
315
|
+
if (data.image !== void 0) events.push({ eventType: AUDIT_EVENT_TYPES.USER_PROFILE_IMAGE_UPDATED });
|
|
316
|
+
if (Object.entries(data).some(([key, value]) => value !== void 0 && !INTERNAL_USER_FIELDS.has(key))) events.push({ eventType: AUDIT_EVENT_TYPES.USER_PROFILE_UPDATED });
|
|
317
|
+
return events;
|
|
318
|
+
}
|
|
319
|
+
async function queryEvents(auth, query) {
|
|
320
|
+
const { limit, offset } = query;
|
|
321
|
+
if (remote) return remote.query(query);
|
|
322
|
+
const where = [];
|
|
323
|
+
if (query.userIds) where.push({
|
|
324
|
+
field: "userId",
|
|
325
|
+
operator: "in",
|
|
326
|
+
value: query.userIds
|
|
327
|
+
});
|
|
328
|
+
if (query.organizationIds) where.push({
|
|
329
|
+
field: "organizationId",
|
|
330
|
+
operator: "in",
|
|
331
|
+
value: query.organizationIds
|
|
332
|
+
});
|
|
333
|
+
for (const field of [
|
|
334
|
+
"eventType",
|
|
335
|
+
"identifier",
|
|
336
|
+
"userId",
|
|
337
|
+
"organizationId"
|
|
338
|
+
]) {
|
|
339
|
+
const value = query[field];
|
|
340
|
+
if (value) where.push({
|
|
341
|
+
field,
|
|
342
|
+
value
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (query.startDate) where.push({
|
|
346
|
+
field: "createdAt",
|
|
347
|
+
operator: "gte",
|
|
348
|
+
value: new Date(query.startDate)
|
|
349
|
+
});
|
|
350
|
+
if (query.endDate) where.push({
|
|
351
|
+
field: "createdAt",
|
|
352
|
+
operator: "lte",
|
|
353
|
+
value: new Date(query.endDate)
|
|
354
|
+
});
|
|
355
|
+
const [events, total] = await Promise.all([auth.adapter.findMany({
|
|
356
|
+
model: "auditLog",
|
|
357
|
+
where,
|
|
358
|
+
limit,
|
|
359
|
+
offset,
|
|
360
|
+
sortBy: {
|
|
361
|
+
field: "createdAt",
|
|
362
|
+
direction: "desc"
|
|
363
|
+
}
|
|
364
|
+
}), auth.adapter.count({
|
|
365
|
+
model: "auditLog",
|
|
366
|
+
where
|
|
367
|
+
})]);
|
|
368
|
+
return {
|
|
369
|
+
events,
|
|
370
|
+
total,
|
|
371
|
+
limit,
|
|
372
|
+
offset
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/** Organization memberships of a user, empty without the organization plugin */
|
|
376
|
+
async function findMemberships(auth, userId) {
|
|
377
|
+
try {
|
|
378
|
+
return await auth.adapter.findMany({
|
|
379
|
+
model: "member",
|
|
380
|
+
where: [{
|
|
381
|
+
field: "userId",
|
|
382
|
+
value: userId
|
|
383
|
+
}]
|
|
384
|
+
});
|
|
385
|
+
} catch {
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function hasRole(roleField, roles) {
|
|
390
|
+
if (typeof roleField !== "string") return false;
|
|
391
|
+
return roleField.split(",").map((role) => role.trim()).some((role) => roles.includes(role));
|
|
392
|
+
}
|
|
393
|
+
async function runQuery(auth, query) {
|
|
394
|
+
try {
|
|
395
|
+
return await queryEvents(auth, query);
|
|
396
|
+
} catch (e) {
|
|
397
|
+
auth.logger.error("[audit-log] Failed to query audit logs", e);
|
|
398
|
+
throw new APIError("SERVICE_UNAVAILABLE", { message: AUDIT_LOG_ERROR_CODES.AUDIT_LOG_UNAVAILABLE });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
id: "audit-log",
|
|
403
|
+
init(ctx) {
|
|
404
|
+
authContext = ctx;
|
|
405
|
+
registerAuditRecorder(ctx, record);
|
|
406
|
+
if (misconfigured) ctx.logger.warn("[audit-log] Missing PAYVO_INFRA_API_URL or PAYVO_INFRA_API_KEY. Remote audit logging is disabled.");
|
|
407
|
+
return { options: { databaseHooks: {
|
|
408
|
+
user: {
|
|
409
|
+
create: { async after(user, context) {
|
|
410
|
+
if (context) {
|
|
411
|
+
const created = createdUsers.get(context) ?? /* @__PURE__ */ new Set();
|
|
412
|
+
created.add(user.id);
|
|
413
|
+
createdUsers.set(context, created);
|
|
414
|
+
}
|
|
415
|
+
await record(context ?? void 0, {
|
|
416
|
+
eventType: AUDIT_EVENT_TYPES.USER_SIGNED_UP,
|
|
417
|
+
userId: user.id,
|
|
418
|
+
identifier: user.email
|
|
419
|
+
});
|
|
420
|
+
} },
|
|
421
|
+
update: {
|
|
422
|
+
async before(data, context) {
|
|
423
|
+
const key = context ?? noContextKey;
|
|
424
|
+
const queue = pendingUserUpdates.get(key) ?? [];
|
|
425
|
+
queue.push(data);
|
|
426
|
+
pendingUserUpdates.set(key, queue);
|
|
427
|
+
return { data };
|
|
428
|
+
},
|
|
429
|
+
async after(user, context) {
|
|
430
|
+
const key = context ?? noContextKey;
|
|
431
|
+
const data = pendingUserUpdates.get(key)?.shift();
|
|
432
|
+
if (!user || !data) return;
|
|
433
|
+
for (const event of userEventsFromUpdate(data)) await record(context ?? void 0, {
|
|
434
|
+
...event,
|
|
435
|
+
userId: user.id,
|
|
436
|
+
identifier: user.email
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
delete: { async after(user, context) {
|
|
441
|
+
await record(context ?? void 0, {
|
|
442
|
+
eventType: AUDIT_EVENT_TYPES.USER_DELETED,
|
|
443
|
+
userId: user.id,
|
|
444
|
+
identifier: user.email
|
|
445
|
+
});
|
|
446
|
+
} }
|
|
447
|
+
},
|
|
448
|
+
session: { create: { async after(session, context) {
|
|
449
|
+
await record(context ?? void 0, {
|
|
450
|
+
eventType: AUDIT_EVENT_TYPES.SESSION_CREATED,
|
|
451
|
+
userId: session.userId
|
|
452
|
+
});
|
|
453
|
+
const impersonatedBy = session.impersonatedBy;
|
|
454
|
+
if (impersonatedBy) await record(context ?? void 0, {
|
|
455
|
+
eventType: AUDIT_EVENT_TYPES.USER_IMPERSONATED,
|
|
456
|
+
userId: session.userId,
|
|
457
|
+
eventData: { impersonatedBy }
|
|
458
|
+
});
|
|
459
|
+
} } },
|
|
460
|
+
account: { create: { async after(account, context) {
|
|
461
|
+
if (context && createdUsers.get(context)?.has(account.userId)) return;
|
|
462
|
+
await record(context ?? void 0, {
|
|
463
|
+
eventType: AUDIT_EVENT_TYPES.ACCOUNT_LINKED,
|
|
464
|
+
userId: account.userId,
|
|
465
|
+
eventData: { providerId: account.providerId }
|
|
466
|
+
});
|
|
467
|
+
} } }
|
|
468
|
+
} } };
|
|
469
|
+
},
|
|
470
|
+
hooks: {
|
|
471
|
+
before: [{
|
|
472
|
+
matcher(context) {
|
|
473
|
+
return context.path === "/sign-out" || context.path in PATH_EVENTS;
|
|
474
|
+
},
|
|
475
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
476
|
+
if (!ctx.headers) return;
|
|
477
|
+
const session = await getSessionFromCtx(ctx).catch(() => null);
|
|
478
|
+
if (session) actors.set(ctx.headers, {
|
|
479
|
+
id: session.user.id,
|
|
480
|
+
email: session.user.email,
|
|
481
|
+
activeOrganizationId: session.session.activeOrganizationId ?? null,
|
|
482
|
+
impersonatedBy: session.session.impersonatedBy ?? null
|
|
483
|
+
});
|
|
484
|
+
const sessionToken = ctx.body?.sessionToken;
|
|
485
|
+
if (ctx.path === "/admin/revoke-user-session" && typeof sessionToken === "string") {
|
|
486
|
+
const revoked = await ctx.context.internalAdapter.findSession(sessionToken).catch(() => null);
|
|
487
|
+
if (revoked) targets.set(ctx.headers, revoked.session.userId);
|
|
488
|
+
}
|
|
489
|
+
})
|
|
490
|
+
}],
|
|
491
|
+
after: [{
|
|
492
|
+
matcher(context) {
|
|
493
|
+
return context.path === "/sign-out" || SIGN_IN_PATHS.includes(context.path) || VERIFICATION_PATHS.includes(context.path) || context.path in PATH_EVENTS;
|
|
494
|
+
},
|
|
495
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
496
|
+
const returned = ctx.context.returned;
|
|
497
|
+
const failed = returned instanceof APIError && returned.statusCode >= 400;
|
|
498
|
+
const body = ctx.body ?? {};
|
|
499
|
+
const identifier = [
|
|
500
|
+
body.email,
|
|
501
|
+
body.username,
|
|
502
|
+
body.phoneNumber
|
|
503
|
+
].find((value) => typeof value === "string") ?? null;
|
|
504
|
+
if (ctx.path === "/sign-out") {
|
|
505
|
+
const actor$1 = ctx.headers && actors.get(ctx.headers);
|
|
506
|
+
if (!actor$1 || failed) return;
|
|
507
|
+
await record(ctx, {
|
|
508
|
+
eventType: AUDIT_EVENT_TYPES.USER_SIGNED_OUT,
|
|
509
|
+
userId: actor$1.id,
|
|
510
|
+
identifier: actor$1.email
|
|
511
|
+
});
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const isCredentialCheck = CREDENTIAL_SIGN_IN_PATHS.includes(ctx.path);
|
|
515
|
+
if (returned instanceof APIError && failed && opts.trackFailedSignIns && (isCredentialCheck || VERIFICATION_PATHS.includes(ctx.path))) {
|
|
516
|
+
await record(ctx, {
|
|
517
|
+
eventType: isCredentialCheck ? AUDIT_EVENT_TYPES.USER_SIGN_IN_FAILED : AUDIT_EVENT_TYPES.VERIFICATION_FAILED,
|
|
518
|
+
status: "failure",
|
|
519
|
+
identifier,
|
|
520
|
+
eventData: {
|
|
521
|
+
code: returned.body?.code,
|
|
522
|
+
status: returned.statusCode
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (SIGN_IN_PATHS.includes(ctx.path)) {
|
|
528
|
+
const newSession = ctx.context.newSession;
|
|
529
|
+
if (!failed && newSession && !(returned && "twoFactorRedirect" in returned)) await record(ctx, {
|
|
530
|
+
eventType: AUDIT_EVENT_TYPES.USER_SIGNED_IN,
|
|
531
|
+
userId: newSession.user.id,
|
|
532
|
+
identifier: newSession.user.email
|
|
533
|
+
});
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const eventType = PATH_EVENTS[ctx.path];
|
|
537
|
+
if (!eventType || failed) return;
|
|
538
|
+
if (ctx.path === "/email-otp/send-verification-otp" && body.type !== "email-verification") return;
|
|
539
|
+
const result = returned ?? {};
|
|
540
|
+
const actor = ctx.headers ? actors.get(ctx.headers) : void 0;
|
|
541
|
+
const target = (ctx.headers && targets.get(ctx.headers)) ?? (ctx.path.startsWith("/admin/") && typeof body.userId === "string" ? body.userId : null);
|
|
542
|
+
const organizationId = await resolveOrganizationId(ctx.context, ctx.path, body, result, actor?.activeOrganizationId ?? null);
|
|
543
|
+
const eventData = {};
|
|
544
|
+
if (ctx.path === "/unlink-account") eventData.providerId = body.providerId;
|
|
545
|
+
if (ctx.path === "/organization/invite-member") {
|
|
546
|
+
eventData.email = body.email;
|
|
547
|
+
eventData.role = body.role;
|
|
548
|
+
}
|
|
549
|
+
if (ctx.path === "/organization/update-member-role") {
|
|
550
|
+
eventData.memberId = body.memberId;
|
|
551
|
+
eventData.role = body.role;
|
|
552
|
+
}
|
|
553
|
+
if (ctx.path.startsWith("/organization/") && ctx.path.endsWith("-role")) eventData.role = body.role ?? body.roleName ?? body.roleId;
|
|
554
|
+
let owner = null;
|
|
555
|
+
if (ctx.path.startsWith("/api-key/")) {
|
|
556
|
+
eventData.keyId = body.keyId ?? result.id;
|
|
557
|
+
owner = result.userId ?? null;
|
|
558
|
+
}
|
|
559
|
+
if (ctx.path.startsWith("/passkey/")) eventData.passkeyId = body.id ?? result.id;
|
|
560
|
+
await record(ctx, {
|
|
561
|
+
eventType,
|
|
562
|
+
userId: target ?? actor?.id ?? result.user?.id ?? owner,
|
|
563
|
+
identifier: actor?.email ?? identifier,
|
|
564
|
+
organizationId,
|
|
565
|
+
eventData: Object.keys(eventData).length ? eventData : null
|
|
566
|
+
});
|
|
567
|
+
})
|
|
568
|
+
}]
|
|
569
|
+
},
|
|
570
|
+
endpoints: {
|
|
571
|
+
getAuditLogs: createAuthEndpoint("/audit-log/get-audit-logs", {
|
|
572
|
+
method: "GET",
|
|
573
|
+
use: [sessionMiddleware],
|
|
574
|
+
query: z$1.object(auditLogQuery).optional(),
|
|
575
|
+
metadata: { openapi: {
|
|
576
|
+
operationId: "getAuditLogs",
|
|
577
|
+
summary: "Get audit logs",
|
|
578
|
+
description: "Get the current user's audit events, optionally restricted to an organization",
|
|
579
|
+
responses: { 200: {
|
|
580
|
+
description: "Paginated audit events",
|
|
581
|
+
content: { "application/json": { schema: {
|
|
582
|
+
type: "object",
|
|
583
|
+
properties: {
|
|
584
|
+
events: {
|
|
585
|
+
type: "array",
|
|
586
|
+
items: {
|
|
587
|
+
type: "object",
|
|
588
|
+
properties: {
|
|
589
|
+
id: { type: "string" },
|
|
590
|
+
eventType: { type: "string" },
|
|
591
|
+
status: { type: "string" },
|
|
592
|
+
userId: {
|
|
593
|
+
type: "string",
|
|
594
|
+
nullable: true
|
|
595
|
+
},
|
|
596
|
+
identifier: {
|
|
597
|
+
type: "string",
|
|
598
|
+
nullable: true
|
|
599
|
+
},
|
|
600
|
+
organizationId: {
|
|
601
|
+
type: "string",
|
|
602
|
+
nullable: true
|
|
603
|
+
},
|
|
604
|
+
path: {
|
|
605
|
+
type: "string",
|
|
606
|
+
nullable: true
|
|
607
|
+
},
|
|
608
|
+
ipAddress: {
|
|
609
|
+
type: "string",
|
|
610
|
+
nullable: true
|
|
611
|
+
},
|
|
612
|
+
userAgent: {
|
|
613
|
+
type: "string",
|
|
614
|
+
nullable: true
|
|
615
|
+
},
|
|
616
|
+
visitorId: {
|
|
617
|
+
type: "string",
|
|
618
|
+
nullable: true
|
|
619
|
+
},
|
|
620
|
+
eventData: {
|
|
621
|
+
type: "object",
|
|
622
|
+
nullable: true
|
|
623
|
+
},
|
|
624
|
+
createdAt: {
|
|
625
|
+
type: "string",
|
|
626
|
+
format: "date-time"
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
total: { type: "number" },
|
|
632
|
+
limit: { type: "number" },
|
|
633
|
+
offset: { type: "number" }
|
|
634
|
+
},
|
|
635
|
+
required: [
|
|
636
|
+
"events",
|
|
637
|
+
"total",
|
|
638
|
+
"limit",
|
|
639
|
+
"offset"
|
|
640
|
+
]
|
|
641
|
+
} } }
|
|
642
|
+
} }
|
|
643
|
+
} }
|
|
644
|
+
}, async (ctx) => {
|
|
645
|
+
const { user } = ctx.context.session;
|
|
646
|
+
const { limit: _limit, offset: _offset,...filters } = ctx.query ?? {};
|
|
647
|
+
const pagination = toPagination(ctx.query ?? {});
|
|
648
|
+
if (filters.userId && filters.userId !== user.id) throw new APIError("FORBIDDEN", { message: AUDIT_LOG_ERROR_CODES.AUDIT_LOG_ACCESS_DENIED });
|
|
649
|
+
return ctx.json(await runQuery(ctx.context, {
|
|
650
|
+
...filters,
|
|
651
|
+
...pagination,
|
|
652
|
+
userId: user.id
|
|
653
|
+
}));
|
|
654
|
+
}),
|
|
655
|
+
getAllAuditLogs: createAuthEndpoint("/audit-log/get-all-audit-logs", {
|
|
656
|
+
method: "GET",
|
|
657
|
+
use: [sessionMiddleware],
|
|
658
|
+
query: z$1.object(auditLogQuery).optional(),
|
|
659
|
+
metadata: { openapi: {
|
|
660
|
+
operationId: "getAllAuditLogs",
|
|
661
|
+
summary: "Get all audit logs",
|
|
662
|
+
description: "Get audit events across organizations the user owns or administers, or every event for admins",
|
|
663
|
+
responses: { 200: {
|
|
664
|
+
description: "Paginated audit events",
|
|
665
|
+
content: { "application/json": { schema: {
|
|
666
|
+
type: "object",
|
|
667
|
+
properties: {
|
|
668
|
+
events: {
|
|
669
|
+
type: "array",
|
|
670
|
+
items: {
|
|
671
|
+
type: "object",
|
|
672
|
+
properties: {
|
|
673
|
+
id: { type: "string" },
|
|
674
|
+
eventType: { type: "string" },
|
|
675
|
+
status: { type: "string" },
|
|
676
|
+
userId: {
|
|
677
|
+
type: "string",
|
|
678
|
+
nullable: true
|
|
679
|
+
},
|
|
680
|
+
identifier: {
|
|
681
|
+
type: "string",
|
|
682
|
+
nullable: true
|
|
683
|
+
},
|
|
684
|
+
organizationId: {
|
|
685
|
+
type: "string",
|
|
686
|
+
nullable: true
|
|
687
|
+
},
|
|
688
|
+
path: {
|
|
689
|
+
type: "string",
|
|
690
|
+
nullable: true
|
|
691
|
+
},
|
|
692
|
+
ipAddress: {
|
|
693
|
+
type: "string",
|
|
694
|
+
nullable: true
|
|
695
|
+
},
|
|
696
|
+
userAgent: {
|
|
697
|
+
type: "string",
|
|
698
|
+
nullable: true
|
|
699
|
+
},
|
|
700
|
+
visitorId: {
|
|
701
|
+
type: "string",
|
|
702
|
+
nullable: true
|
|
703
|
+
},
|
|
704
|
+
eventData: {
|
|
705
|
+
type: "object",
|
|
706
|
+
nullable: true
|
|
707
|
+
},
|
|
708
|
+
createdAt: {
|
|
709
|
+
type: "string",
|
|
710
|
+
format: "date-time"
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
},
|
|
715
|
+
total: { type: "number" },
|
|
716
|
+
limit: { type: "number" },
|
|
717
|
+
offset: { type: "number" }
|
|
718
|
+
},
|
|
719
|
+
required: [
|
|
720
|
+
"events",
|
|
721
|
+
"total",
|
|
722
|
+
"limit",
|
|
723
|
+
"offset"
|
|
724
|
+
]
|
|
725
|
+
} } }
|
|
726
|
+
} }
|
|
727
|
+
} }
|
|
728
|
+
}, async (ctx) => {
|
|
729
|
+
const { user } = ctx.context.session;
|
|
730
|
+
const { limit: _limit, offset: _offset,...filters } = ctx.query ?? {};
|
|
731
|
+
const pagination = toPagination(ctx.query ?? {});
|
|
732
|
+
if (hasRole(user.role, opts.adminRoles)) return ctx.json(await runQuery(ctx.context, {
|
|
733
|
+
...filters,
|
|
734
|
+
...pagination
|
|
735
|
+
}));
|
|
736
|
+
const administered = (await findMemberships(ctx.context, user.id)).filter((m) => hasRole(m.role, ["owner", "admin"])).map((m) => m.organizationId);
|
|
737
|
+
if (!administered.length || filters.organizationId && !administered.includes(filters.organizationId)) throw new APIError("FORBIDDEN", { message: AUDIT_LOG_ERROR_CODES.AUDIT_LOG_ACCESS_DENIED });
|
|
738
|
+
return ctx.json(await runQuery(ctx.context, {
|
|
739
|
+
...filters,
|
|
740
|
+
...pagination,
|
|
741
|
+
organizationIds: filters.organizationId ? void 0 : administered
|
|
742
|
+
}));
|
|
743
|
+
}),
|
|
744
|
+
getEventTypes: createAuthEndpoint("/audit-log/get-event-types", {
|
|
745
|
+
method: "GET",
|
|
746
|
+
use: [sessionMiddleware],
|
|
747
|
+
metadata: { openapi: {
|
|
748
|
+
operationId: "getAuditLogEventTypes",
|
|
749
|
+
summary: "Get audit log event types",
|
|
750
|
+
description: "Get the event types the audit log can record",
|
|
751
|
+
responses: { 200: {
|
|
752
|
+
description: "Event types",
|
|
753
|
+
content: { "application/json": { schema: {
|
|
754
|
+
type: "object",
|
|
755
|
+
properties: { types: {
|
|
756
|
+
type: "array",
|
|
757
|
+
items: { type: "string" }
|
|
758
|
+
} },
|
|
759
|
+
required: ["types"]
|
|
760
|
+
} } }
|
|
761
|
+
} }
|
|
762
|
+
} }
|
|
763
|
+
}, async (ctx) => ctx.json({ types: ALL_EVENT_TYPES }))
|
|
764
|
+
},
|
|
765
|
+
schema: mergedSchema,
|
|
766
|
+
$ERROR_CODES: AUDIT_LOG_ERROR_CODES,
|
|
767
|
+
options: opts
|
|
768
|
+
};
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
//#endregion
|
|
772
|
+
export { auditLog as n, AUDIT_LOG_ERROR_CODES as t };
|