@nextsparkjs/core 0.1.0-beta.195 → 0.1.0-beta.196
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/components/billing/InvoicesPagination.js +2 -2
- package/dist/components/dashboard/navigation/DynamicNavigation.js +2 -2
- package/dist/components/settings/layouts/SettingsSidebar.d.ts.map +1 -1
- package/dist/components/settings/layouts/SettingsSidebar.js +0 -1
- package/dist/components/teams/CreateTeamButton.js +2 -2
- package/dist/components/teams/CreateTeamDialog.js +1 -1
- package/dist/components/teams/InviteMemberButton.js +3 -3
- package/dist/components/teams/InviteMemberDialog.js +1 -1
- package/dist/components/teams/TeamPendingInvitations.js +2 -2
- package/dist/lib/api/api-error.d.ts +2 -0
- package/dist/lib/api/api-error.d.ts.map +1 -1
- package/dist/lib/api/api-error.js +3 -1
- package/dist/lib/api/auth/dual-auth.d.ts +2 -1
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +22 -4
- package/dist/lib/api/auth/scope-policy.d.ts +6 -3
- package/dist/lib/api/auth/scope-policy.d.ts.map +1 -1
- package/dist/lib/api/cors-response.d.ts +3 -2
- package/dist/lib/api/cors-response.d.ts.map +1 -1
- package/dist/lib/api/cors-response.js +8 -6
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +0 -3
- package/dist/lib/api/helpers.d.ts.map +1 -1
- package/dist/lib/api/helpers.js +5 -22
- package/dist/lib/api/permission-middleware.d.ts.map +1 -1
- package/dist/lib/api/permission-middleware.js +0 -2
- package/dist/lib/api/rate-limit.d.ts +8 -0
- package/dist/lib/api/rate-limit.d.ts.map +1 -1
- package/dist/lib/api/rate-limit.js +34 -3
- package/dist/lib/api/request-origin.d.ts.map +1 -1
- package/dist/lib/api/request-origin.js +1 -1
- package/dist/lib/auth/runtime-readiness.d.ts.map +1 -1
- package/dist/lib/auth/runtime-readiness.js +2 -0
- package/dist/lib/auth/sample-accounts.d.ts +17 -0
- package/dist/lib/auth/sample-accounts.d.ts.map +1 -0
- package/dist/lib/auth/sample-accounts.js +22 -0
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +6 -3
- package/dist/lib/cache/public-entity-cache.d.ts +21 -0
- package/dist/lib/cache/public-entity-cache.d.ts.map +1 -0
- package/dist/lib/cache/public-entity-cache.js +25 -0
- package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
- package/dist/lib/entities/entity-hooks.js +4 -0
- package/dist/lib/mcp/errors.d.ts +2 -0
- package/dist/lib/mcp/errors.d.ts.map +1 -1
- package/dist/lib/mcp/errors.js +3 -1
- package/dist/lib/mcp/index.d.ts +1 -1
- package/dist/lib/mcp/index.d.ts.map +1 -1
- package/dist/lib/mcp/index.js +2 -1
- package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
- package/dist/lib/services/generic-entity.service.js +2 -0
- package/dist/lib/teams/actions.d.ts.map +1 -1
- package/dist/lib/teams/actions.js +0 -1
- package/dist/messages/de/billing.json +12 -1
- package/dist/messages/de/common.json +2 -0
- package/dist/messages/de/docs.json +1 -0
- package/dist/messages/de/features.json +16 -4
- package/dist/messages/de/home.json +5 -0
- package/dist/messages/de/index.d.ts +68 -25
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/de/index.js +2 -0
- package/dist/messages/de/index.ts +2 -0
- package/dist/messages/de/settings.json +1 -0
- package/dist/messages/de/teams.json +3 -18
- package/dist/messages/en/billing.json +1 -1
- package/dist/messages/en/common.json +2 -0
- package/dist/messages/en/docs.json +1 -0
- package/dist/messages/en/features.json +16 -4
- package/dist/messages/en/home.json +5 -0
- package/dist/messages/en/index.d.ts +58 -25
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/en/index.js +2 -0
- package/dist/messages/en/index.ts +2 -0
- package/dist/messages/en/settings.json +1 -0
- package/dist/messages/en/teams.json +3 -18
- package/dist/messages/es/billing.json +1 -1
- package/dist/messages/es/common.json +2 -0
- package/dist/messages/es/docs.json +1 -0
- package/dist/messages/es/features.json +16 -4
- package/dist/messages/es/home.json +5 -0
- package/dist/messages/es/index.d.ts +41 -8
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/es/index.js +2 -0
- package/dist/messages/es/index.ts +2 -0
- package/dist/messages/es/settings.json +1 -0
- package/dist/messages/es/teams.json +3 -0
- package/dist/messages/fr/billing.json +12 -1
- package/dist/messages/fr/common.json +2 -0
- package/dist/messages/fr/docs.json +1 -0
- package/dist/messages/fr/features.json +16 -4
- package/dist/messages/fr/home.json +5 -0
- package/dist/messages/fr/index.d.ts +68 -25
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/fr/index.js +2 -0
- package/dist/messages/fr/index.ts +2 -0
- package/dist/messages/fr/settings.json +1 -0
- package/dist/messages/fr/teams.json +3 -18
- package/dist/messages/it/billing.json +12 -1
- package/dist/messages/it/common.json +2 -0
- package/dist/messages/it/docs.json +1 -0
- package/dist/messages/it/features.json +16 -4
- package/dist/messages/it/home.json +5 -0
- package/dist/messages/it/index.d.ts +68 -25
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/it/index.js +2 -0
- package/dist/messages/it/index.ts +2 -0
- package/dist/messages/it/settings.json +1 -0
- package/dist/messages/it/teams.json +3 -18
- package/dist/messages/pt/billing.json +12 -1
- package/dist/messages/pt/common.json +2 -0
- package/dist/messages/pt/docs.json +1 -0
- package/dist/messages/pt/features.json +16 -4
- package/dist/messages/pt/home.json +5 -0
- package/dist/messages/pt/index.d.ts +68 -25
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/messages/pt/index.js +2 -0
- package/dist/messages/pt/index.ts +2 -0
- package/dist/messages/pt/settings.json +1 -0
- package/dist/messages/pt/teams.json +3 -18
- package/dist/migrations/029_sample_accounts_outside_development.sql +55 -0
- package/dist/migrations/090_sample_data.sql +10 -0
- package/dist/routes/_internal/entity-permission-layout.d.ts.map +1 -1
- package/dist/routes/_internal/entity-permission-layout.js +0 -7
- package/dist/routes/api/mcp/route.d.ts.map +1 -1
- package/dist/routes/api/mcp/route.js +7 -1
- package/dist/routes/api/v1/media/upload/route.d.ts +2 -1
- package/dist/routes/api/v1/media/upload/route.d.ts.map +1 -1
- package/dist/routes/api/v1/media/upload/route.js +10 -2
- package/dist/routes/api/v1/teams/[teamId]/members/route.d.ts.map +1 -1
- package/dist/routes/api/v1/teams/[teamId]/members/route.js +12 -10
- package/dist/routes/api/v1/teams/switch/route.d.ts.map +1 -1
- package/dist/routes/api/v1/teams/switch/route.js +4 -1
- package/dist/routes/dashboard/settings/password/page.d.ts.map +1 -1
- package/dist/routes/dashboard/settings/password/page.js +10 -12
- package/dist/routes/devtools/config/page.js +1 -1
- package/dist/routes/devtools/page.js +1 -1
- package/dist/routes/devtools/scheduled-actions/page.js +1 -1
- package/dist/routes/devtools/tests/[[...path]]/page.js +1 -1
- package/dist/templates/ci-workflows/github/cypress-regression.yml +1 -1
- package/dist/templates/ci-workflows/github/cypress-smoke.yml +2 -1
- package/dist/templates/projects/starter/README.md +2 -0
- package/dist/templates/projects/starter/docs/public/01-overview/01-introduction.md +2 -0
- package/dist/templates/projects/starter/templates/dashboard/analytics/layout.tsx +14 -0
- package/dist/templates/proxy.ts +15 -8
- package/dist/templates/public/uploads/README.md +5 -94
- package/migrations/029_sample_accounts_outside_development.sql +55 -0
- package/migrations/090_sample_data.sql +10 -0
- package/package.json +2 -2
- package/scripts/build/registry/host/__tests__/host-proxy-areas.test.mjs +72 -1
- package/scripts/build/registry/host/proxy-areas.mjs +65 -2
- package/scripts/db/run-migrations.mjs +30 -10
- package/scripts/db/sample-data.mjs +44 -0
- package/scripts/db/verify-theme-migrations.mjs +6 -0
- package/templates/ci-workflows/github/cypress-regression.yml +1 -1
- package/templates/ci-workflows/github/cypress-smoke.yml +2 -1
- package/templates/projects/starter/README.md +2 -0
- package/templates/projects/starter/docs/public/01-overview/01-introduction.md +2 -0
- package/templates/projects/starter/templates/dashboard/analytics/layout.tsx +14 -0
- package/templates/proxy.ts +15 -8
- package/templates/public/uploads/README.md +5 -94
- package/tests/jest/__mocks__/@nextsparkjs/registries/api-presets-registry.ts +2 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/mcp-registry.ts +2 -0
- package/dist/templates/projects/starter/public/brand/.gitkeep +0 -0
- package/dist/templates/public/uploads/.gitignore +0 -6
- package/dist/templates/public/uploads/temp/.gitkeep +0 -1
- package/templates/projects/starter/public/brand/.gitkeep +0 -0
- package/templates/public/uploads/.gitignore +0 -6
- package/templates/public/uploads/temp/.gitkeep +0 -1
|
@@ -32,9 +32,9 @@ function InvoicesPagination({
|
|
|
32
32
|
{
|
|
33
33
|
className: "flex items-center justify-between pt-4 border-t",
|
|
34
34
|
"data-cy": "invoices-pagination",
|
|
35
|
-
"aria-label": t("showing", {
|
|
35
|
+
"aria-label": t("showing", { start: showingFrom, end: showingTo, total: totalInvoices }),
|
|
36
36
|
children: [
|
|
37
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("showing", {
|
|
37
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("showing", { start: showingFrom, end: showingTo, total: totalInvoices }) }),
|
|
38
38
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
39
39
|
/* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("page", { current: currentPage, total: totalPages }) }),
|
|
40
40
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
|
|
@@ -167,11 +167,11 @@ function DynamicNavigation({
|
|
|
167
167
|
isMobile && "w-full"
|
|
168
168
|
),
|
|
169
169
|
"aria-current": pathname === "/dashboard" ? "page" : void 0,
|
|
170
|
-
title: isCollapsed ? t("
|
|
170
|
+
title: isCollapsed ? t("dashboard") : void 0,
|
|
171
171
|
"data-cy": sel("dashboard.navigation.dashboardLink"),
|
|
172
172
|
children: [
|
|
173
173
|
/* @__PURE__ */ jsx(Home, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
174
|
-
!isCollapsed && /* @__PURE__ */ jsx("span", { className: "truncate", children: t("
|
|
174
|
+
!isCollapsed && /* @__PURE__ */ jsx("span", { className: "truncate", children: t("dashboard") })
|
|
175
175
|
]
|
|
176
176
|
}
|
|
177
177
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SettingsSidebar.d.ts","sourceRoot":"","sources":["../../../../src/components/settings/layouts/SettingsSidebar.tsx"],"names":[],"mappings":"AAoCA,UAAU,oBAAoB;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"SettingsSidebar.d.ts","sourceRoot":"","sources":["../../../../src/components/settings/layouts/SettingsSidebar.tsx"],"names":[],"mappings":"AAoCA,UAAU,oBAAoB;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE,oBAAoB,2CAiKlE"}
|
|
@@ -50,7 +50,6 @@ function SettingsSidebar({ className }) {
|
|
|
50
50
|
}, [prefetchProfile, prefetchTeams]);
|
|
51
51
|
const filteredNavigation = useMemo(() => {
|
|
52
52
|
const enabledPages = getEnabledSettingsPages();
|
|
53
|
-
if (process.env.NODE_ENV === "development") console.log("[SettingsSidebar] enabledPages:", enabledPages);
|
|
54
53
|
return enabledPages.filter((page) => {
|
|
55
54
|
if (page.key === "password" && !hasPassword) {
|
|
56
55
|
return false;
|
|
@@ -30,10 +30,10 @@ function CreateTeamButton({
|
|
|
30
30
|
className,
|
|
31
31
|
onClick,
|
|
32
32
|
"data-cy": "create-team-button",
|
|
33
|
-
"aria-label": t("create
|
|
33
|
+
"aria-label": t("actions.create"),
|
|
34
34
|
children: [
|
|
35
35
|
showIcon && /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-2", "aria-hidden": "true" }),
|
|
36
|
-
children || t("create
|
|
36
|
+
children || t("actions.create")
|
|
37
37
|
]
|
|
38
38
|
}
|
|
39
39
|
);
|
|
@@ -39,7 +39,7 @@ function CreateTeamDialog({ open, onOpenChange }) {
|
|
|
39
39
|
setSlug("");
|
|
40
40
|
setDescription("");
|
|
41
41
|
} catch (error) {
|
|
42
|
-
toast.error(error instanceof Error ? error.message : t("errors.
|
|
42
|
+
toast.error(error instanceof Error ? error.message : t("errors.permissionDenied"));
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
const handleNameChange = (value) => {
|
|
@@ -39,7 +39,7 @@ function InviteMemberButton({
|
|
|
39
39
|
"aria-label": t("invite.limitReached", { limit: maxMembersPerTeam }),
|
|
40
40
|
children: [
|
|
41
41
|
showIcon && /* @__PURE__ */ jsx(UserPlus, { className: "h-4 w-4 mr-2", "aria-hidden": "true" }),
|
|
42
|
-
children || t("invite
|
|
42
|
+
children || t("actions.invite")
|
|
43
43
|
]
|
|
44
44
|
}
|
|
45
45
|
) }),
|
|
@@ -54,10 +54,10 @@ function InviteMemberButton({
|
|
|
54
54
|
className,
|
|
55
55
|
onClick,
|
|
56
56
|
"data-cy": "invite-member-button",
|
|
57
|
-
"aria-label": t("invite
|
|
57
|
+
"aria-label": t("actions.invite"),
|
|
58
58
|
children: [
|
|
59
59
|
showIcon && /* @__PURE__ */ jsx(UserPlus, { className: "h-4 w-4 mr-2", "aria-hidden": "true" }),
|
|
60
|
-
children || t("invite
|
|
60
|
+
children || t("actions.invite")
|
|
61
61
|
]
|
|
62
62
|
}
|
|
63
63
|
);
|
|
@@ -49,7 +49,7 @@ function InviteMemberDialog({ open, onOpenChange, teamId, currentUserRole = "own
|
|
|
49
49
|
setEmail("");
|
|
50
50
|
setRole(defaultRole);
|
|
51
51
|
} catch (error) {
|
|
52
|
-
toast.error(error instanceof Error ? error.message : t("errors.
|
|
52
|
+
toast.error(error instanceof Error ? error.message : t("errors.permissionDenied"));
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsx(DialogContent, { "data-cy": "invite-member-dialog", children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
|
|
@@ -73,11 +73,11 @@ function TeamPendingInvitations({ teamId }) {
|
|
|
73
73
|
},
|
|
74
74
|
onSuccess: () => {
|
|
75
75
|
queryClient.invalidateQueries({ queryKey: ["team-invitations", teamId] });
|
|
76
|
-
toast.success(t("
|
|
76
|
+
toast.success(t("invitations.cancelSuccess"));
|
|
77
77
|
setCancellingId(null);
|
|
78
78
|
},
|
|
79
79
|
onError: (error) => {
|
|
80
|
-
toast.error(error instanceof Error ? error.message : t("errors.
|
|
80
|
+
toast.error(error instanceof Error ? error.message : t("errors.permissionDenied"));
|
|
81
81
|
setCancellingId(null);
|
|
82
82
|
}
|
|
83
83
|
});
|
|
@@ -11,6 +11,8 @@ export declare const API_ERROR_CODES: {
|
|
|
11
11
|
readonly INSUFFICIENT_SCOPE: "INSUFFICIENT_SCOPE";
|
|
12
12
|
/** Route declared no required API key scope, so API keys are rejected (fail closed, #93) */
|
|
13
13
|
readonly SCOPE_NOT_DECLARED: "SCOPE_NOT_DECLARED";
|
|
14
|
+
/** Production upload with no media storage provider configured (BLOB_READ_WRITE_TOKEN) */
|
|
15
|
+
readonly STORAGE_NOT_CONFIGURED: "STORAGE_NOT_CONFIGURED";
|
|
14
16
|
};
|
|
15
17
|
export type ApiErrorCode = typeof API_ERROR_CODES[keyof typeof API_ERROR_CODES];
|
|
16
18
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../../../src/lib/api/api-error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,eAAe;IAC1B,2DAA2D;;IAE3D,0FAA0F;;IAE1F,4FAA4F;;
|
|
1
|
+
{"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../../../src/lib/api/api-error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,eAAe;IAC1B,2DAA2D;;IAE3D,0FAA0F;;IAE1F,4FAA4F;;IAE5F,0FAA0F;;CAElF,CAAA;AAEV,MAAM,MAAM,YAAY,GAAG,OAAO,eAAe,CAAC,MAAM,OAAO,eAAe,CAAC,CAAA;AAE/E;;;;;GAKG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;gBAEL,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE;IAa5F;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAI7B;;;;;OAKG;WACU,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAqC1F"}
|
|
@@ -4,7 +4,9 @@ const API_ERROR_CODES = {
|
|
|
4
4
|
/** API key lacks the scope the route declared (authenticateRequest / hasRequiredScope) */
|
|
5
5
|
INSUFFICIENT_SCOPE: "INSUFFICIENT_SCOPE",
|
|
6
6
|
/** Route declared no required API key scope, so API keys are rejected (fail closed, #93) */
|
|
7
|
-
SCOPE_NOT_DECLARED: "SCOPE_NOT_DECLARED"
|
|
7
|
+
SCOPE_NOT_DECLARED: "SCOPE_NOT_DECLARED",
|
|
8
|
+
/** Production upload with no media storage provider configured (BLOB_READ_WRITE_TOKEN) */
|
|
9
|
+
STORAGE_NOT_CONFIGURED: "STORAGE_NOT_CONFIGURED"
|
|
8
10
|
};
|
|
9
11
|
class ApiError extends Error {
|
|
10
12
|
code;
|
|
@@ -47,6 +47,7 @@ export interface DualAuthResult {
|
|
|
47
47
|
keyId?: string;
|
|
48
48
|
/** The session's id (type 'session' only): the active team cookie counts only for it. */
|
|
49
49
|
sessionId?: string;
|
|
50
|
+
/** The 429 to return when a valid API key is over its per-key limit (`success` is false, `error` says 429). */
|
|
50
51
|
rateLimitResponse?: Response;
|
|
51
52
|
/** Set when a dev-only x-act-as-user override replaced the real caller. */
|
|
52
53
|
actingAs?: {
|
|
@@ -126,7 +127,7 @@ export declare function createAuthError(message?: string, status?: number): Next
|
|
|
126
127
|
* Turn a failed `authenticateRequest` result into the right HTTP response:
|
|
127
128
|
* 401 `AUTHENTICATION_FAILED` when nothing usable was presented, 403
|
|
128
129
|
* `INSUFFICIENT_SCOPE` / `SCOPE_NOT_DECLARED` when a valid API key was
|
|
129
|
-
* rejected by scope enforcement. Use it in the `!authResult.success` branch
|
|
130
|
+
* rejected by scope enforcement, the 429 when the key is over its limit. Use it in the `!authResult.success` branch
|
|
130
131
|
* instead of a hand-written 401, so scope rejections are not mislabeled.
|
|
131
132
|
*/
|
|
132
133
|
export declare function createAuthFailureResponse(authResult: DualAuthResult): NextResponse;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dual-auth.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/dual-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"dual-auth.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/dual-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAOvD,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAKrB,MAAM,gBAAgB,CAAA;AAEvB,YAAY,EAAE,mBAAmB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAK/F;;;GAGG;AACH,eAAO,MAAM,oBAAoB,uBAAuB,CAAA;AAExD;;GAEG;AACH,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AACnD,eAAO,MAAM,kBAAkB,8BAA8B,CAAA;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAOjD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAA;IACpC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,mHAAmH;IACnH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+GAA+G;IAC/G,iBAAiB,CAAC,EAAE,QAAQ,CAAA;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3D,0EAA0E;IAC1E,KAAK,CAAC,EAAE,eAAe,CAAA;CACxB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,cAAc,CAAC,CA2DzB;AAmKD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAUtG;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,cAAc,GACzB,OAAO,CAAC,MAAM,GAAG,YAAY,CAAC,CAsChC;AAMD;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,cAAc,EAC1B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAkBlB;AA2BD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,MAAkC,EAAE,MAAM,GAAE,MAAY;;;;GAShG;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,cAAc,GAAG,YAAY,CAWlF"}
|
|
@@ -3,6 +3,7 @@ import { getUserDefaultTeamId } from "../../teams/dashboard-team.js";
|
|
|
3
3
|
import { NextResponse } from "next/server";
|
|
4
4
|
import { auth } from "../../auth.js";
|
|
5
5
|
import { validateApiKey } from "../auth.js";
|
|
6
|
+
import { apiKeyRateLimitResponse } from "../rate-limit.js";
|
|
6
7
|
import { queryOne } from "../../db.js";
|
|
7
8
|
import { TeamMemberService } from "../../services/team-member.service.js";
|
|
8
9
|
import { TeamService } from "../../services/team.service.js";
|
|
@@ -31,6 +32,26 @@ async function authenticateRequest(request, options = {}) {
|
|
|
31
32
|
error: failure
|
|
32
33
|
};
|
|
33
34
|
}
|
|
35
|
+
const rateLimitResponse = apiKeyRateLimitResponse(request, {
|
|
36
|
+
userId: apiKeyResult.user.id,
|
|
37
|
+
keyId: apiKeyResult.keyId,
|
|
38
|
+
scopes: apiKeyResult.scopes ?? []
|
|
39
|
+
});
|
|
40
|
+
if (rateLimitResponse) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
type: "api-key",
|
|
44
|
+
user: apiKeyResult.user,
|
|
45
|
+
scopes: apiKeyResult.scopes,
|
|
46
|
+
keyId: apiKeyResult.keyId,
|
|
47
|
+
rateLimitResponse,
|
|
48
|
+
error: {
|
|
49
|
+
code: "RATE_LIMIT_EXCEEDED",
|
|
50
|
+
status: 429,
|
|
51
|
+
message: "Rate limit exceeded"
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
34
55
|
return applyActAsOverride(request, apiKeyResult);
|
|
35
56
|
}
|
|
36
57
|
const sessionResult = await trySessionAuth(request);
|
|
@@ -120,15 +141,11 @@ async function tryApiKeyAuth(request) {
|
|
|
120
141
|
async function trySessionAuth(request) {
|
|
121
142
|
var _a;
|
|
122
143
|
try {
|
|
123
|
-
console.log("[dual-auth] Attempting session authentication...");
|
|
124
144
|
const session = await auth.api.getSession({ headers: request.headers });
|
|
125
|
-
console.log("[dual-auth] Session result:", session ? "found" : "not found");
|
|
126
145
|
if (!(session == null ? void 0 : session.user)) {
|
|
127
|
-
console.log("[dual-auth] No session.user found");
|
|
128
146
|
return { success: false, type: "none", user: null };
|
|
129
147
|
}
|
|
130
148
|
const defaultTeamId = await getUserDefaultTeamId(session.user.id);
|
|
131
|
-
console.log("[dual-auth] Session auth successful for user:", session.user.id);
|
|
132
149
|
return {
|
|
133
150
|
success: true,
|
|
134
151
|
type: "session",
|
|
@@ -223,6 +240,7 @@ function createAuthError(message = "Authentication required", status = 401) {
|
|
|
223
240
|
);
|
|
224
241
|
}
|
|
225
242
|
function createAuthFailureResponse(authResult) {
|
|
243
|
+
if (authResult.rateLimitResponse) return authResult.rateLimitResponse;
|
|
226
244
|
const failure = authResult.error ?? authenticationFailed();
|
|
227
245
|
return NextResponse.json(
|
|
228
246
|
{
|
|
@@ -22,11 +22,14 @@
|
|
|
22
22
|
* visible 403 on an integration instead of a silently over-privileged
|
|
23
23
|
* credential. Before, `scopes` were populated and nobody was obliged to look.
|
|
24
24
|
*/
|
|
25
|
-
export type DualAuthFailureCode = 'AUTHENTICATION_FAILED' | 'INSUFFICIENT_SCOPE' | 'SCOPE_NOT_DECLARED';
|
|
25
|
+
export type DualAuthFailureCode = 'AUTHENTICATION_FAILED' | 'INSUFFICIENT_SCOPE' | 'SCOPE_NOT_DECLARED' | 'RATE_LIMIT_EXCEEDED';
|
|
26
26
|
export interface DualAuthFailure {
|
|
27
27
|
code: DualAuthFailureCode;
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* 401 when no usable credential was presented, 403 when a valid key was rejected on scope, 429 when a valid
|
|
30
|
+
* key is over its per-key limit.
|
|
31
|
+
*/
|
|
32
|
+
status: 401 | 403 | 429;
|
|
30
33
|
message: string;
|
|
31
34
|
}
|
|
32
35
|
export interface AuthenticateOptions {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-policy.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/scope-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,MAAM,mBAAmB,GAC3B,uBAAuB,GACvB,oBAAoB,GACpB,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"scope-policy.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/scope-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,MAAM,mBAAmB,GAC3B,uBAAuB,GACvB,oBAAoB,GACpB,oBAAoB,GACpB,qBAAqB,CAAA;AAEzB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAA;IACzB;;;OAGG;IACH,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACjC;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,eAAe,CAEtD;AAED,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,mBAAmB,CAAC,eAAe,CAAC,GAAG,MAAM,EAAE,CAKrG;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACrC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,GAC/B,OAAO,CAIT;AAED,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAC/B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAU/D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACrC,OAAO,EAAE,mBAAmB,EAC5B,KAAK,EAAE,MAAM,GACZ,eAAe,GAAG,IAAI,CAuBxB"}
|
|
@@ -13,7 +13,7 @@ export declare const CORS_ALLOW_HEADERS: string;
|
|
|
13
13
|
* cookie-authenticated writes only from it (checkRequestOrigin).
|
|
14
14
|
*/
|
|
15
15
|
export declare function isAppOnlyCorsPath(pathname: string): boolean;
|
|
16
|
-
/** A request's path, for corsGrant (undefined when its URL does not parse). */
|
|
16
|
+
/** A request's path, for corsGrant (undefined when its URL does not parse, which corsGrant treats as app-only). */
|
|
17
17
|
export declare function requestPathname(request: Request): string | undefined;
|
|
18
18
|
/** The app's own origins: NEXT_PUBLIC_APP_URL and BETTER_AUTH_URL. */
|
|
19
19
|
export declare function appOrigins(): string[];
|
|
@@ -26,7 +26,8 @@ export declare function appOrigins(): string[];
|
|
|
26
26
|
* response names no origin and sends no credentials header.
|
|
27
27
|
* Under the admin and developer areas (isAppOnlyCorsPath, given the request's
|
|
28
28
|
* `pathname`), only the app's own origin is granted, with credentials; no
|
|
29
|
-
* other origin, listed or not.
|
|
29
|
+
* other origin, listed or not. A `pathname` that is undefined (not known)
|
|
30
|
+
* gets the same answer as those areas.
|
|
30
31
|
*/
|
|
31
32
|
export declare function corsGrant(origin: string, config: CorsConfig, env: string, pathname?: string): {
|
|
32
33
|
origin: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors-response.d.ts","sourceRoot":"","sources":["../../../src/lib/api/cors-response.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAG/D,KAAK,UAAU,GAAG,IAAI,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAA;AAEhD,eAAO,MAAM,kBAAkB,2CAA2C,CAAA;AAC1E,eAAO,MAAM,kBAAkB,QAAuF,CAAA;AAKtH;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"cors-response.d.ts","sourceRoot":"","sources":["../../../src/lib/api/cors-response.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAG/D,KAAK,UAAU,GAAG,IAAI,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAA;AAEhD,eAAO,MAAM,kBAAkB,2CAA2C,CAAA;AAC1E,eAAO,MAAM,kBAAkB,QAAuF,CAAA;AAKtH;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAM3D;AAED,mHAAmH;AACnH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAEpE;AAED,sEAAsE;AACtE,wBAAgB,UAAU,IAAI,MAAM,EAAE,CAKrC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,GAChB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAYjD;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAGnD;AAED,oGAAoG;AACpG,wBAAgB,cAAc,CAC5B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GACrD,IAAI,CAWN;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,CAAC,CAa7E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,CAE5D"}
|
|
@@ -6,11 +6,13 @@ const CORS_ALLOW_METHODS = "GET, POST, PUT, PATCH, DELETE, OPTIONS";
|
|
|
6
6
|
const CORS_ALLOW_HEADERS = ["Content-Type", "Authorization", "X-API-Key", ...CLIENT_REQUEST_HEADERS].join(", ");
|
|
7
7
|
const APP_ONLY_PATH = /(^|\/)api\/(v1\/)?(superadmin|devtools)(\/|$)/;
|
|
8
8
|
function isAppOnlyCorsPath(pathname) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const path = pathname.split("/").map((segment) => {
|
|
10
|
+
try {
|
|
11
|
+
return decodeURIComponent(segment);
|
|
12
|
+
} catch {
|
|
13
|
+
return segment;
|
|
14
|
+
}
|
|
15
|
+
}).join("/");
|
|
14
16
|
return APP_ONLY_PATH.test(path.toLowerCase().replace(/\/{2,}/g, "/"));
|
|
15
17
|
}
|
|
16
18
|
function requestPathname(request) {
|
|
@@ -32,7 +34,7 @@ function appOrigins() {
|
|
|
32
34
|
}
|
|
33
35
|
function corsGrant(origin, config, env, pathname) {
|
|
34
36
|
const normalizedOrigin = normalizeOrigin(origin);
|
|
35
|
-
if (pathname
|
|
37
|
+
if (pathname === void 0 || isAppOnlyCorsPath(pathname)) {
|
|
36
38
|
return appOrigins().includes(normalizedOrigin) ? { origin: normalizedOrigin, credentials: true } : null;
|
|
37
39
|
}
|
|
38
40
|
const listed = isOriginAllowed(normalizedOrigin, getCorsOrigins(config, env));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generic-handler.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/entity/generic-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"generic-handler.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/entity/generic-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AA4wBvD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAEnF;AA8lBD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAErF;AAyaD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAEpJ;AAoND;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAEtJ;AA6ZD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAEtJ;AAwKD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAEtF"}
|
|
@@ -350,13 +350,11 @@ async function checkAuthPermission(authResult, entitySlug, action, teamId, reque
|
|
|
350
350
|
}
|
|
351
351
|
async function validateTeamMembership(userId, teamId) {
|
|
352
352
|
try {
|
|
353
|
-
console.log("[GenericHandler] Validating team membership:", { userId, teamId, teamIdLength: teamId == null ? void 0 : teamId.length });
|
|
354
353
|
const member = await queryOneWithRLS(
|
|
355
354
|
'SELECT id FROM "team_members" WHERE "teamId" = $1 AND "userId" = $2',
|
|
356
355
|
[teamId, userId],
|
|
357
356
|
userId
|
|
358
357
|
);
|
|
359
|
-
console.log("[GenericHandler] Team membership result:", { found: !!member, memberId: member == null ? void 0 : member.id });
|
|
360
358
|
return !!member;
|
|
361
359
|
} catch (error) {
|
|
362
360
|
console.error("[GenericHandler] Error validating team membership:", error);
|
|
@@ -367,7 +365,6 @@ async function validateTeamContextWithBypass(request, authResult, userId) {
|
|
|
367
365
|
const teamId = getTeamIdFromRequest(request);
|
|
368
366
|
const canBypass = await canBypassTeamContext(authResult, request);
|
|
369
367
|
if (canBypass) {
|
|
370
|
-
console.log("[GenericHandler] Admin bypass active:", { userId, teamId: teamId || "cross-team" });
|
|
371
368
|
return { valid: true, teamId, isBypass: true };
|
|
372
369
|
}
|
|
373
370
|
if (!teamId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/api/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,UAAU,EAAsC,MAAM,QAAQ,CAAC;AAaxE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EACL,KAAK,mBAAmB,EAKzB,MAAM,qBAAqB,CAAC;AAG7B,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,OAAO,mBAAmB,EAAE,QAAQ,EAAE,CAAC;IAC9C,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,KAAK,IAAI,GAAG,UAAU,GAAG,WAAW,CAAC;AAErC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC;IACT,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,iBAAiB,CAAC,EAAE,YAAY,CAAC;IACjC,wEAAwE;IACxE,aAAa,CAAC,EAAE,YAAY,CAAC;CAC9B,CAAC,CAwDD;AAgFD;;;;;;;;GAQG;AACH,wBAAsB,iCAAiC,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC;IACrF,IAAI,EAAE,UAAU,CAAC;IACjB,iBAAiB,CAAC,EAAE,YAAY,CAAC;CAClC,CAAC,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/api/helpers.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/api/helpers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { getValidatedApiKey, validateApiKey } from "./auth.js";
|
|
3
3
|
import { mutateWithRLS, queryWithRLS } from "../db.js";
|
|
4
|
-
import {
|
|
4
|
+
import { apiKeyRateLimitResponse } from "./rate-limit.js";
|
|
5
5
|
import { getRecordedClientIp } from "./client-ip.js";
|
|
6
6
|
import { getApplicationConfig } from "../config/index.js";
|
|
7
7
|
import { auth } from "../auth.js";
|
|
@@ -36,7 +36,6 @@ async function validateAndAuthenticateRequest(request, options = {}) {
|
|
|
36
36
|
flags: userFlags,
|
|
37
37
|
isSession: true
|
|
38
38
|
};
|
|
39
|
-
console.log(`[Auth] Session authentication successful for user ${session.user.id} with role ${userRole}`);
|
|
40
39
|
return { auth: sessionAuth };
|
|
41
40
|
}
|
|
42
41
|
} catch (sessionError) {
|
|
@@ -58,7 +57,8 @@ async function validateAndAuthenticateRequest(request, options = {}) {
|
|
|
58
57
|
)
|
|
59
58
|
};
|
|
60
59
|
}
|
|
61
|
-
|
|
60
|
+
const rateLimitResponse = apiKeyRateLimitResponse(request, apiKeyAuth);
|
|
61
|
+
return rateLimitResponse ? { auth: apiKeyAuth, rateLimitResponse } : { auth: apiKeyAuth };
|
|
62
62
|
}
|
|
63
63
|
function generateScopesForRole(role, flags = []) {
|
|
64
64
|
const baseScopes = getBaseScopesFromRegistry();
|
|
@@ -103,25 +103,8 @@ async function validateAndAuthenticateApiRequest(request) {
|
|
|
103
103
|
if (!auth2) {
|
|
104
104
|
throw new Error("Invalid API key");
|
|
105
105
|
}
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
function applyApiKeyRateLimit(auth2) {
|
|
109
|
-
const rateLimitResult = checkRateLimit(auth2.keyId);
|
|
110
|
-
if (!rateLimitResult.allowed) {
|
|
111
|
-
const response = NextResponse.json(
|
|
112
|
-
{
|
|
113
|
-
success: false,
|
|
114
|
-
error: "Rate limit exceeded",
|
|
115
|
-
message: `Too many requests. Limit: ${rateLimitResult.limit} requests per minute`,
|
|
116
|
-
code: "RATE_LIMIT_EXCEEDED",
|
|
117
|
-
retryAfter: Math.ceil((rateLimitResult.resetTime - Date.now()) / 1e3)
|
|
118
|
-
},
|
|
119
|
-
{ status: 429 }
|
|
120
|
-
);
|
|
121
|
-
addRateLimitHeaders(response, auth2.keyId, auth2.scopes);
|
|
122
|
-
return { auth: auth2, rateLimitResponse: response };
|
|
123
|
-
}
|
|
124
|
-
return { auth: auth2 };
|
|
106
|
+
const rateLimitResponse = apiKeyRateLimitResponse(request, auth2);
|
|
107
|
+
return rateLimitResponse ? { auth: auth2, rateLimitResponse } : { auth: auth2 };
|
|
125
108
|
}
|
|
126
109
|
function getApiAuth(request) {
|
|
127
110
|
const auth2 = getValidatedApiKey(request);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-middleware.d.ts","sourceRoot":"","sources":["../../../src/lib/api/permission-middleware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAc1C;;GAEG;AACH,eAAO,MAAM,WAAW,SAAS,CAAA;AAEjC;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAElF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,MAAM,CAOjF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"permission-middleware.d.ts","sourceRoot":"","sources":["../../../src/lib/api/permission-middleware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAc1C;;GAEG;AACH,eAAO,MAAM,WAAW,SAAS,CAAA;AAEjC;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAElF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,MAAM,CAOjF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC,CA8BtE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,OAAO,GACpB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAK9B"}
|
|
@@ -21,9 +21,7 @@ function getActionFromMethod(method, isCollection) {
|
|
|
21
21
|
}
|
|
22
22
|
async function checkEntityPermission(userId, teamId, entitySlug, action) {
|
|
23
23
|
const permission = getEntityPermission(entitySlug, action);
|
|
24
|
-
console.log(`[PermMiddleware] Checking permission: ${permission} for user ${userId} in team ${teamId}`);
|
|
25
24
|
const hasPermission = await checkPermission(userId, teamId, permission);
|
|
26
|
-
console.log(`[PermMiddleware] Result: ${hasPermission}`);
|
|
27
25
|
if (!hasPermission) {
|
|
28
26
|
return {
|
|
29
27
|
allowed: false,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { type ApiKeyAuth } from './auth';
|
|
2
3
|
import { type RateLimitTier } from '../rate-limit-redis';
|
|
3
4
|
export interface RateLimitResult {
|
|
4
5
|
allowed: boolean;
|
|
@@ -10,6 +11,13 @@ export interface RateLimitResult {
|
|
|
10
11
|
* Verifica el rate limit para un identificador específico
|
|
11
12
|
*/
|
|
12
13
|
export declare function checkRateLimit(identifier: string, limit?: number, windowMs?: number): RateLimitResult;
|
|
14
|
+
/**
|
|
15
|
+
* The per-key limit (checkRateLimit, by the key's id) for a request authenticated with `auth`: a 429 ready to
|
|
16
|
+
* return when the key is over it, null otherwise. Counted once per request, however many times the request is
|
|
17
|
+
* authenticated (authenticateRequest, validateAndAuthenticate*Request). The per-address limit of the route
|
|
18
|
+
* wrapper (withRateLimitTier) still applies on top. It is a safety cap, so DISABLE_RATE_LIMITING does not turn it off.
|
|
19
|
+
*/
|
|
20
|
+
export declare function apiKeyRateLimitResponse(request: Request, auth: ApiKeyAuth): NextResponse | null;
|
|
13
21
|
/**
|
|
14
22
|
* Rate limit types for distributed rate limiting
|
|
15
23
|
* Re-export from rate-limit-redis for convenience
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/lib/api/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/lib/api/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGzD,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAI7B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,KAAK,GAAE,MAAa,EACpB,QAAQ,GAAE,MAAc,GACvB,eAAe,CAyCjB;AAKD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,YAAY,GAAG,IAAI,CA0B/F;AAED;;;GAGG;AACH,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,aAAqB,GAC1B,OAAO,CAAC,eAAe,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6CpD;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,eAAe,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,CAwB5G;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,IAAI,OAAO,CAEzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAAE,GACf,YAAY,GAAG,IAAI,CAkCrB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAAE,GACf,YAAY,CASd;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,EAC/C,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,IAEtD,SAAS,WAAW,EAAE,GAAG,MAAM,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC,CA2BvE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB,GAAG,IAAI,CAaP;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAGlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,KAAK,CAAC;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,CASD;AAED;;GAEG;AACH,wBAAgB,sBAAsB;;;;;EAErC;AA4DD,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EAAE,EACnD,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,EACpE,IAAI,GAAE,aAAqB,IAqCb,SAAS,WAAW,EAAE,GAAG,MAAM,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC,CAIvE"}
|
|
@@ -43,6 +43,34 @@ function checkRateLimit(identifier, limit = 1e3, windowMs = 6e4) {
|
|
|
43
43
|
limit
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
+
const apiKeyChecks = /* @__PURE__ */ new WeakMap();
|
|
47
|
+
function apiKeyRateLimitResponse(request, auth) {
|
|
48
|
+
let result = apiKeyChecks.get(request);
|
|
49
|
+
if (!result) {
|
|
50
|
+
result = checkRateLimit(auth.keyId);
|
|
51
|
+
apiKeyChecks.set(request, result);
|
|
52
|
+
}
|
|
53
|
+
if (result.allowed) return null;
|
|
54
|
+
const retryAfter = Math.ceil((result.resetTime - Date.now()) / 1e3);
|
|
55
|
+
return NextResponse.json(
|
|
56
|
+
{
|
|
57
|
+
success: false,
|
|
58
|
+
error: "Rate limit exceeded",
|
|
59
|
+
message: `Too many requests. Limit: ${result.limit} requests per minute`,
|
|
60
|
+
code: "RATE_LIMIT_EXCEEDED",
|
|
61
|
+
retryAfter
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
status: 429,
|
|
65
|
+
headers: {
|
|
66
|
+
"X-RateLimit-Limit": result.limit.toString(),
|
|
67
|
+
"X-RateLimit-Remaining": "0",
|
|
68
|
+
"X-RateLimit-Reset": result.resetTime.toString(),
|
|
69
|
+
"Retry-After": retryAfter.toString()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
46
74
|
async function checkDistributedRateLimit(identifier, type = "api") {
|
|
47
75
|
if (isRateLimitingDisabled()) {
|
|
48
76
|
return { allowed: true, remaining: 999, resetTime: Date.now() + 6e4, limit: 999 };
|
|
@@ -225,9 +253,11 @@ function withRateLimitTier(handler, tier = "api") {
|
|
|
225
253
|
return createRateLimitErrorResponse(rateLimitResult);
|
|
226
254
|
}
|
|
227
255
|
const response = await handler(request, ...args);
|
|
228
|
-
response.
|
|
229
|
-
|
|
230
|
-
|
|
256
|
+
if (response.status !== 429) {
|
|
257
|
+
response.headers.set("X-RateLimit-Limit", rateLimitResult.limit.toString());
|
|
258
|
+
response.headers.set("X-RateLimit-Remaining", Math.max(0, rateLimitResult.remaining).toString());
|
|
259
|
+
response.headers.set("X-RateLimit-Reset", rateLimitResult.resetTime.toString());
|
|
260
|
+
}
|
|
231
261
|
return response;
|
|
232
262
|
};
|
|
233
263
|
return async (request, ...args) => {
|
|
@@ -237,6 +267,7 @@ function withRateLimitTier(handler, tier = "api") {
|
|
|
237
267
|
}
|
|
238
268
|
export {
|
|
239
269
|
addRateLimitHeaders,
|
|
270
|
+
apiKeyRateLimitResponse,
|
|
240
271
|
applyRateLimit,
|
|
241
272
|
checkDistributedRateLimit,
|
|
242
273
|
checkRateLimit,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-origin.d.ts","sourceRoot":"","sources":["../../../src/lib/api/request-origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"request-origin.d.ts","sourceRoot":"","sources":["../../../src/lib/api/request-origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAgEvD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY,GAAG,WAAW,CAoBnF"}
|
|
@@ -36,7 +36,7 @@ function withoutSessionCookies(request) {
|
|
|
36
36
|
}
|
|
37
37
|
function isTrustedWriteOrigin(request, origin) {
|
|
38
38
|
const path = requestPathname(request);
|
|
39
|
-
if (path
|
|
39
|
+
if (path === void 0 || isAppOnlyCorsPath(path)) {
|
|
40
40
|
const lan = normalizeCorsEnvironment(process.env.NODE_ENV || "development") !== "production" && isPrivateLanOrigin(origin);
|
|
41
41
|
return lan || appOrigins().includes(origin);
|
|
42
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-readiness.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/runtime-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAGpB,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"runtime-readiness.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/runtime-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAGpB,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAIlE,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,aAAa,CAAA;AAEpB,KAAK,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;AAE5D,MAAM,WAAW,2BAA2B;IAC1C,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,WAAW,GAAG,kBAAkB,CAAC,GAAG,IAAI,CAAA;IAClF,GAAG,CAAC,EAAE,kBAAkB,CAAA;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,kFAAkF;IAClF,wBAAwB,EAAE,OAAO,CAAA;IACjC,mFAAmF;IACnF,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,OAAO,GAAG,aAAa,CAAA;IAC/B,gBAAgB,EAAE,eAAe,EAAE,CAAA;IACnC,YAAY,EAAE,sBAAsB,CAAA;CACrC;AAaD,qEAAqE;AACrE,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,2BAAgC,GAAG,mBAAmB,CAEtG;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,2BAAgC,GAAG,IAAI,CAgBzF;AAED,sFAAsF;AACtF,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,2BAAgC,GAAG,mBAAmB,CAYrG;AAiBD,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,2BAAgC,GAAG,OAAO,CAE1F;AAED,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,2BAAgC,GAAG,OAAO,CAE3F;AAkDD;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA4E1B;AAED,sFAAsF;AACtF,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,EAChC,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,QAAQ,CAAC,CAGnB"}
|
|
@@ -2,6 +2,7 @@ import "server-only";
|
|
|
2
2
|
import { AUTH_CONFIG } from "../config/index.js";
|
|
3
3
|
import { isPasswordLoginEnabled } from "./auth-methods.js";
|
|
4
4
|
import { warnClientIpSourceAtStartup } from "../api/client-ip.js";
|
|
5
|
+
import { warnSampleAccountsAtStartup } from "./sample-accounts.js";
|
|
5
6
|
import {
|
|
6
7
|
authReadinessConfigurationFromEnv,
|
|
7
8
|
evaluateAuthReadiness
|
|
@@ -21,6 +22,7 @@ function getRuntimeAuthReadiness(options = {}) {
|
|
|
21
22
|
}
|
|
22
23
|
function logAuthReadinessAtStartup(options = {}) {
|
|
23
24
|
warnClientIpSourceAtStartup(options.env ?? process.env);
|
|
25
|
+
void warnSampleAccountsAtStartup(options.env ?? process.env);
|
|
24
26
|
try {
|
|
25
27
|
const env = options.env ?? process.env;
|
|
26
28
|
if (env.NODE_ENV !== "production") return;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The password hashes the sample data ships with (core's 090_sample_data.sql and
|
|
3
|
+
* the project templates' sample data). A "credential" account that still holds
|
|
4
|
+
* one of them can be signed in to with a documented password.
|
|
5
|
+
* Keep in sync with migrations/029_sample_accounts_outside_development.sql.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SAMPLE_PASSWORD_HASHES: readonly ["22de14d5472248ed0bece911df908b2a:d29576424798ba6845d348a3767c0b0f38a00f2aca461b3b1d34b99a93cab06c86774c6edb183e6d6ec47457649b032a49a7b60a48f6f4f7fbbc4ea40258f19f", "3db9e98e2b4d3caca97fdf2783791cbc:34b293de615caf277a237773208858e960ea8aa10f1f5c5c309b632f192cac34d52ceafbd338385616f4929e4b1b6c055b67429c6722ffdb80b01d9bf4764866"];
|
|
8
|
+
type QueryRows = (text: string, params?: unknown[]) => Promise<Array<{
|
|
9
|
+
count: number;
|
|
10
|
+
}>>;
|
|
11
|
+
/**
|
|
12
|
+
* In production, logs one warning when accounts with a sample password exist.
|
|
13
|
+
* Never throws: a database that cannot be reached at startup is not this check's to report.
|
|
14
|
+
*/
|
|
15
|
+
export declare function warnSampleAccountsAtStartup(env?: Record<string, string | undefined>, queryRows?: QueryRows): Promise<void>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=sample-accounts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sample-accounts.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/sample-accounts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,qVAGzB,CAAA;AAEV,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAA;AAExF;;;GAGG;AACH,wBAAsB,2BAA2B,CAC/C,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,IAAI,CAAC,CAcf"}
|