@nextsparkjs/core 0.1.0-beta.166 → 0.1.0-beta.168
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/hooks/useAuth.d.ts +2 -1
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +13 -8
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +23 -3
- package/dist/lib/auth-context.d.ts +5 -0
- package/dist/lib/auth-context.d.ts.map +1 -1
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +28 -5
- package/dist/lib/config/app.config.d.ts.map +1 -1
- package/dist/lib/config/app.config.js +9 -1
- package/dist/lib/config/types.d.ts +17 -0
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/db.d.ts +28 -5
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +38 -16
- package/dist/lib/entities/types.d.ts +20 -3
- package/dist/lib/entities/types.d.ts.map +1 -1
- package/dist/lib/services/team-member.service.d.ts.map +1 -1
- package/dist/lib/services/team-member.service.js +2 -1
- package/dist/lib/services/team.service.d.ts.map +1 -1
- package/dist/lib/services/team.service.js +2 -2
- package/dist/lib/teams/actions.d.ts.map +1 -1
- package/dist/lib/teams/actions.js +4 -3
- package/dist/migrations/001_better_auth_and_functions.sql +70 -0
- package/dist/migrations/002_auth_tables.sql +90 -10
- package/dist/migrations/007_teams_table.sql +10 -4
- package/dist/migrations/008_team_members_table.sql +1 -1
- package/dist/migrations/009_team_invitations_table.sql +1 -1
- package/dist/migrations/010_teams_functions_triggers.sql +6 -48
- package/dist/migrations/013_billing_subscriptions.sql +61 -31
- package/dist/migrations/016_billing_events.sql +17 -3
- package/dist/migrations/017_scheduled_actions_table.sql +9 -7
- package/dist/migrations/022_rls_runtime_roles.sql +87 -0
- package/dist/styles/classes.json +1 -1
- package/dist/templates/app/api/auth/[...all]/route.ts +14 -1
- package/migrations/001_better_auth_and_functions.sql +70 -0
- package/migrations/002_auth_tables.sql +90 -10
- package/migrations/007_teams_table.sql +10 -4
- package/migrations/008_team_members_table.sql +1 -1
- package/migrations/009_team_invitations_table.sql +1 -1
- package/migrations/010_teams_functions_triggers.sql +6 -48
- package/migrations/013_billing_subscriptions.sql +61 -31
- package/migrations/016_billing_events.sql +17 -3
- package/migrations/017_scheduled_actions_table.sql +9 -7
- package/migrations/022_rls_runtime_roles.sql +87 -0
- package/package.json +2 -2
- package/scripts/db/run-migrations.mjs +13 -2
- package/templates/app/api/auth/[...all]/route.ts +14 -1
- package/templates/next.config.mjs +1 -4
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- 022 · RLS runtime roles — nextspark_app (non-owner runtime role) + grants
|
|
3
|
+
-- ============================================================================
|
|
4
|
+
-- Generic framework migration: creates the non-owner runtime role the app
|
|
5
|
+
-- connects as so RLS is actually evaluated on the app path.
|
|
6
|
+
--
|
|
7
|
+
-- WHY: by default the app connects to Postgres as the table OWNER, so RLS
|
|
8
|
+
-- policies are never evaluated on the app path (owner skips RLS unless FORCE).
|
|
9
|
+
-- This migration creates the non-owner runtime role the app connects as after
|
|
10
|
+
-- the runtime cutover. Until the cutover nothing changes for the running app:
|
|
11
|
+
-- migrations and seeds keep running as the owner.
|
|
12
|
+
--
|
|
13
|
+
-- Design decisions:
|
|
14
|
+
-- - nextspark_app is NOLOGIN here; the LOGIN credential is created per environment
|
|
15
|
+
-- at cutover time (deploy-time secret), never in a migration.
|
|
16
|
+
-- - nextspark_app is a member of `authenticated` (INHERIT): every existing policy
|
|
17
|
+
-- declared `TO authenticated` applies to it without rewriting.
|
|
18
|
+
-- - NO `FORCE ROW LEVEL SECURITY`: nextspark_app is not the owner so plain ENABLE
|
|
19
|
+
-- is enough, and FORCE would break owner-run seeds/sample-data on Supabase
|
|
20
|
+
-- (where `postgres` is owner but not superuser).
|
|
21
|
+
-- - NO `BYPASSRLS` role here: that attribute requires superuser (not available
|
|
22
|
+
-- on Supabase). The service context for machine actors (webhooks, scheduled
|
|
23
|
+
-- actions) is a CORE/environment workstream: the app uses a separate service
|
|
24
|
+
-- connection (DATABASE_SERVICE_URL) for system operations.
|
|
25
|
+
-- - `anon` gets an explicit REVOKE + default privileges revoke: on Supabase the
|
|
26
|
+
-- default privileges grant to anon automatically; abstaining is not enough.
|
|
27
|
+
--
|
|
28
|
+
-- Ordering: runs after all core table DDL (<= 021) so `GRANT ... ON ALL TABLES`
|
|
29
|
+
-- covers every existing table; `ALTER DEFAULT PRIVILEGES` covers tables created
|
|
30
|
+
-- later (theme/entity migrations) by the same migration owner.
|
|
31
|
+
|
|
32
|
+
-- ----------------------------------------------------------------------------
|
|
33
|
+
-- 1. Runtime role
|
|
34
|
+
-- ----------------------------------------------------------------------------
|
|
35
|
+
DO $$
|
|
36
|
+
BEGIN
|
|
37
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'nextspark_app') THEN
|
|
38
|
+
CREATE ROLE nextspark_app NOLOGIN NOINHERIT;
|
|
39
|
+
END IF;
|
|
40
|
+
END $$;
|
|
41
|
+
|
|
42
|
+
-- INHERIT membership in `authenticated` so policies `TO authenticated` apply.
|
|
43
|
+
ALTER ROLE nextspark_app INHERIT;
|
|
44
|
+
GRANT authenticated TO nextspark_app;
|
|
45
|
+
|
|
46
|
+
-- Allow the migration/validation user to SET ROLE nextspark_app (e.g. an RLS
|
|
47
|
+
-- isolation test suite can run its checks as this role without a LOGIN credential).
|
|
48
|
+
DO $$
|
|
49
|
+
BEGIN
|
|
50
|
+
EXECUTE format('GRANT nextspark_app TO %I', current_user);
|
|
51
|
+
EXCEPTION WHEN OTHERS THEN
|
|
52
|
+
RAISE NOTICE 'GRANT nextspark_app TO current_user skipped: %', SQLERRM;
|
|
53
|
+
END $$;
|
|
54
|
+
|
|
55
|
+
-- ----------------------------------------------------------------------------
|
|
56
|
+
-- 2. Grants for nextspark_app (RLS does the row filtering; grants gate the tables)
|
|
57
|
+
-- ----------------------------------------------------------------------------
|
|
58
|
+
GRANT USAGE ON SCHEMA public TO nextspark_app;
|
|
59
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO nextspark_app;
|
|
60
|
+
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO nextspark_app;
|
|
61
|
+
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO nextspark_app;
|
|
62
|
+
|
|
63
|
+
-- Future objects created by the migration owner inherit the same grants.
|
|
64
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
65
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO nextspark_app;
|
|
66
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
67
|
+
GRANT USAGE, SELECT ON SEQUENCES TO nextspark_app;
|
|
68
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
69
|
+
GRANT EXECUTE ON FUNCTIONS TO nextspark_app;
|
|
70
|
+
|
|
71
|
+
-- ----------------------------------------------------------------------------
|
|
72
|
+
-- 3. anon: explicit lockdown (defense for PostgREST/Data API surfaces)
|
|
73
|
+
-- ----------------------------------------------------------------------------
|
|
74
|
+
DO $$
|
|
75
|
+
BEGIN
|
|
76
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'anon') THEN
|
|
77
|
+
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM anon;
|
|
78
|
+
REVOKE ALL ON ALL SEQUENCES IN SCHEMA public FROM anon;
|
|
79
|
+
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA public FROM anon;
|
|
80
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE ALL ON TABLES FROM anon;
|
|
81
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon;
|
|
82
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon;
|
|
83
|
+
END IF;
|
|
84
|
+
END $$;
|
|
85
|
+
|
|
86
|
+
COMMENT ON ROLE nextspark_app IS
|
|
87
|
+
'Non-owner runtime role for the NextSpark app. Member of authenticated (policies TO authenticated apply). RLS is evaluated for every query once DATABASE_URL connects as this role instead of the table owner.';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.168",
|
|
4
4
|
"description": "NextSpark - The complete SaaS framework for Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "NextSpark <hello@nextspark.dev>",
|
|
@@ -469,7 +469,7 @@
|
|
|
469
469
|
"tailwind-merge": "^3.3.1",
|
|
470
470
|
"uuid": "^13.0.0",
|
|
471
471
|
"zod": "^4.1.5",
|
|
472
|
-
"@nextsparkjs/testing": "0.1.0-beta.
|
|
472
|
+
"@nextsparkjs/testing": "0.1.0-beta.168"
|
|
473
473
|
},
|
|
474
474
|
"scripts": {
|
|
475
475
|
"postinstall": "node scripts/postinstall.mjs || true",
|
|
@@ -21,6 +21,11 @@ const rootDir = projectRoot; // For backward compatibility with code below
|
|
|
21
21
|
const envPath = path.join(projectRoot, '.env');
|
|
22
22
|
|
|
23
23
|
let DATABASE_URL = process.env.DATABASE_URL ?? null;
|
|
24
|
+
// Migrations and seeds run as the table OWNER. After the runtime cutover the app
|
|
25
|
+
// connects DATABASE_URL as the non-owner `nextspark_app` role, so the owner
|
|
26
|
+
// credential for migrations is provided separately via MIGRATE_DATABASE_URL.
|
|
27
|
+
// Falls back to DATABASE_URL when unset (pre-cutover: same owner connection).
|
|
28
|
+
let MIGRATE_DATABASE_URL = process.env.MIGRATE_DATABASE_URL ?? null;
|
|
24
29
|
let ACTIVE_THEME = process.env.NEXT_PUBLIC_ACTIVE_THEME ?? null;
|
|
25
30
|
|
|
26
31
|
if (fs.existsSync(envPath)) {
|
|
@@ -35,6 +40,9 @@ if (fs.existsSync(envPath)) {
|
|
|
35
40
|
if (key?.trim() === 'DATABASE_URL' && valueParts.length > 0) {
|
|
36
41
|
DATABASE_URL = value;
|
|
37
42
|
}
|
|
43
|
+
if (key?.trim() === 'MIGRATE_DATABASE_URL' && valueParts.length > 0) {
|
|
44
|
+
MIGRATE_DATABASE_URL = value;
|
|
45
|
+
}
|
|
38
46
|
if (key?.trim() === 'NEXT_PUBLIC_ACTIVE_THEME' && valueParts.length > 0) {
|
|
39
47
|
ACTIVE_THEME = value;
|
|
40
48
|
}
|
|
@@ -47,6 +55,9 @@ if (!DATABASE_URL) {
|
|
|
47
55
|
process.exit(1);
|
|
48
56
|
}
|
|
49
57
|
|
|
58
|
+
// The URL used for the actual migration connection (owner credential).
|
|
59
|
+
const MIGRATION_URL = MIGRATE_DATABASE_URL || DATABASE_URL;
|
|
60
|
+
|
|
50
61
|
if (!ACTIVE_THEME) {
|
|
51
62
|
console.error("❌ NEXT_PUBLIC_ACTIVE_THEME not found in environment variables");
|
|
52
63
|
process.exit(1);
|
|
@@ -54,7 +65,7 @@ if (!ACTIVE_THEME) {
|
|
|
54
65
|
|
|
55
66
|
async function runMigrations() {
|
|
56
67
|
const client = new Client({
|
|
57
|
-
connectionString:
|
|
68
|
+
connectionString: MIGRATION_URL,
|
|
58
69
|
ssl: {
|
|
59
70
|
rejectUnauthorized: false,
|
|
60
71
|
require: true
|
|
@@ -359,7 +370,7 @@ async function executeEntityMigration(client, migration) {
|
|
|
359
370
|
// Entity migrations runner - WordPress-like architecture with sample_data deferred execution
|
|
360
371
|
async function runEntityMigrations() {
|
|
361
372
|
const client = new Client({
|
|
362
|
-
connectionString:
|
|
373
|
+
connectionString: MIGRATION_URL,
|
|
363
374
|
ssl: {
|
|
364
375
|
rejectUnauthorized: false,
|
|
365
376
|
require: true
|
|
@@ -8,6 +8,7 @@ import { isPublicSignupRestricted } from "@nextsparkjs/core/lib/teams/helpers";
|
|
|
8
8
|
import { TeamService } from "@nextsparkjs/core/lib/services";
|
|
9
9
|
import { wrapAuthHandlerWithCors, handleCorsPreflightRequest, addCorsHeaders } from "@nextsparkjs/core/lib/api/helpers";
|
|
10
10
|
import { checkDistributedRateLimit } from "@nextsparkjs/core/lib/api/rate-limit";
|
|
11
|
+
import { withSignupContext } from "@nextsparkjs/core/lib/auth-context";
|
|
11
12
|
|
|
12
13
|
const handlers = toNextJsHandler(auth);
|
|
13
14
|
|
|
@@ -137,6 +138,18 @@ export async function POST(req: NextRequest) {
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
// Read the optional signup intent (`x-signup-intent` header) and run the signup
|
|
142
|
+
// within request-scoped context so the user.create.after hook can map it to an
|
|
143
|
+
// initial team role (AUTH_CONFIG.signupIntent).
|
|
144
|
+
const signupIntent = isSignupAttempt
|
|
145
|
+
? (req.headers.get('x-signup-intent') || undefined)
|
|
146
|
+
: undefined;
|
|
147
|
+
|
|
140
148
|
// Wrap with CORS headers for cross-origin requests (mobile apps, etc.)
|
|
141
|
-
return wrapAuthHandlerWithCors(
|
|
149
|
+
return wrapAuthHandlerWithCors(
|
|
150
|
+
signupIntent
|
|
151
|
+
? () => withSignupContext({ signupIntent }, () => handlers.POST(req))
|
|
152
|
+
: () => handlers.POST(req),
|
|
153
|
+
req
|
|
154
|
+
);
|
|
142
155
|
}
|
|
@@ -16,10 +16,7 @@ const nextConfig = {
|
|
|
16
16
|
ignoreBuildErrors: true,
|
|
17
17
|
},
|
|
18
18
|
transpilePackages: ['@nextsparkjs/core'],
|
|
19
|
-
|
|
20
|
-
// async_hooks). e.g. the AI plugin's @anthropic-ai/claude-agent-sdk. Listing
|
|
21
|
-
// a package here is harmless when it isn't installed.
|
|
22
|
-
serverExternalPackages: ['handlebars', '@anthropic-ai/claude-agent-sdk'],
|
|
19
|
+
serverExternalPackages: ['handlebars'],
|
|
23
20
|
turbopack: {
|
|
24
21
|
root: __dirname,
|
|
25
22
|
},
|