@actuate-media/cms-core 0.38.0 → 0.40.0
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/__tests__/api/ai-status.test.d.ts +2 -0
- package/dist/__tests__/api/ai-status.test.d.ts.map +1 -0
- package/dist/__tests__/api/ai-status.test.js +110 -0
- package/dist/__tests__/api/ai-status.test.js.map +1 -0
- package/dist/__tests__/api/users-detail.test.d.ts +2 -0
- package/dist/__tests__/api/users-detail.test.d.ts.map +1 -0
- package/dist/__tests__/api/users-detail.test.js +349 -0
- package/dist/__tests__/api/users-detail.test.js.map +1 -0
- package/dist/__tests__/api/users-invite.test.d.ts +2 -0
- package/dist/__tests__/api/users-invite.test.d.ts.map +1 -0
- package/dist/__tests__/api/users-invite.test.js +221 -0
- package/dist/__tests__/api/users-invite.test.js.map +1 -0
- package/dist/__tests__/auth/invites.test.d.ts +2 -0
- package/dist/__tests__/auth/invites.test.d.ts.map +1 -0
- package/dist/__tests__/auth/invites.test.js +181 -0
- package/dist/__tests__/auth/invites.test.js.map +1 -0
- package/dist/__tests__/auth/reset.test.js +52 -1
- package/dist/__tests__/auth/reset.test.js.map +1 -1
- package/dist/__tests__/auth/roles.test.d.ts +2 -0
- package/dist/__tests__/auth/roles.test.d.ts.map +1 -0
- package/dist/__tests__/auth/roles.test.js +60 -0
- package/dist/__tests__/auth/roles.test.js.map +1 -0
- package/dist/__tests__/auth/team.test.d.ts +2 -0
- package/dist/__tests__/auth/team.test.d.ts.map +1 -0
- package/dist/__tests__/auth/team.test.js +139 -0
- package/dist/__tests__/auth/team.test.js.map +1 -0
- package/dist/__tests__/auth/user-admin.test.d.ts +2 -0
- package/dist/__tests__/auth/user-admin.test.d.ts.map +1 -0
- package/dist/__tests__/auth/user-admin.test.js +162 -0
- package/dist/__tests__/auth/user-admin.test.js.map +1 -0
- package/dist/__tests__/email/sender.test.d.ts +2 -0
- package/dist/__tests__/email/sender.test.d.ts.map +1 -0
- package/dist/__tests__/email/sender.test.js +33 -0
- package/dist/__tests__/email/sender.test.js.map +1 -0
- package/dist/__tests__/middleware.test.js +30 -0
- package/dist/__tests__/middleware.test.js.map +1 -1
- package/dist/__tests__/setup/index.test.js +37 -2
- package/dist/__tests__/setup/index.test.js.map +1 -1
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +1116 -194
- package/dist/api/handlers.js.map +1 -1
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/invite-email.d.ts +12 -0
- package/dist/auth/invite-email.d.ts.map +1 -0
- package/dist/auth/invite-email.js +57 -0
- package/dist/auth/invite-email.js.map +1 -0
- package/dist/auth/invites.d.ts +71 -0
- package/dist/auth/invites.d.ts.map +1 -0
- package/dist/auth/invites.js +279 -0
- package/dist/auth/invites.js.map +1 -0
- package/dist/auth/reset.d.ts +6 -0
- package/dist/auth/reset.d.ts.map +1 -1
- package/dist/auth/reset.js +38 -7
- package/dist/auth/reset.js.map +1 -1
- package/dist/auth/roles.d.ts +51 -0
- package/dist/auth/roles.d.ts.map +1 -0
- package/dist/auth/roles.js +159 -0
- package/dist/auth/roles.js.map +1 -0
- package/dist/auth/team.d.ts +86 -0
- package/dist/auth/team.d.ts.map +1 -0
- package/dist/auth/team.js +270 -0
- package/dist/auth/team.js.map +1 -0
- package/dist/auth/user-admin.d.ts +91 -0
- package/dist/auth/user-admin.d.ts.map +1 -0
- package/dist/auth/user-admin.js +250 -0
- package/dist/auth/user-admin.js.map +1 -0
- package/dist/email/sender.d.ts +35 -0
- package/dist/email/sender.d.ts.map +1 -0
- package/dist/email/sender.js +25 -0
- package/dist/email/sender.js.map +1 -0
- package/dist/forms/notify.d.ts.map +1 -1
- package/dist/forms/notify.js +3 -2
- package/dist/forms/notify.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +60 -1
- package/dist/middleware.js.map +1 -1
- package/dist/setup/index.d.ts +14 -0
- package/dist/setup/index.d.ts.map +1 -1
- package/dist/setup/index.js +29 -3
- package/dist/setup/index.js.map +1 -1
- package/package.json +11 -1
- package/prisma/migrations/0011_user_roles_owner_viewer/migration.sql +6 -0
- package/prisma/migrations/0012_invites_and_user_lifecycle/migration.sql +40 -0
- package/prisma/schema.prisma +43 -0
package/dist/middleware.js
CHANGED
|
@@ -34,8 +34,13 @@ export function createCMSMiddleware(cmsConfig) {
|
|
|
34
34
|
return new Response('Forbidden', { status: 403 });
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
// CMS_SECRET is mandatory: it signs session JWTs, so the admin (including
|
|
38
|
+
// the setup wizard, which auto-logs-in the new admin) cannot function
|
|
39
|
+
// without it. Rather than a bare 500, return an actionable page that tells
|
|
40
|
+
// the operator exactly what to set. The setup *API* route is already
|
|
41
|
+
// bypassed above, so this only guards the admin UI.
|
|
37
42
|
if (isAdminRoute && !secret) {
|
|
38
|
-
return
|
|
43
|
+
return configMissingResponse();
|
|
39
44
|
}
|
|
40
45
|
// Session verification for admin routes (not API routes — those handle their own auth)
|
|
41
46
|
if (isAdminRoute && secret) {
|
|
@@ -60,6 +65,60 @@ export function createCMSMiddleware(cmsConfig) {
|
|
|
60
65
|
return null;
|
|
61
66
|
};
|
|
62
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Actionable error page shown when the admin UI is requested but `CMS_SECRET`
|
|
70
|
+
* is not configured. Self-contained HTML (no external assets, no theme access)
|
|
71
|
+
* because it renders before the admin shell can load. Deliberately leaks no
|
|
72
|
+
* secret values — only the variable name and remediation steps.
|
|
73
|
+
*/
|
|
74
|
+
function configMissingResponse() {
|
|
75
|
+
const html = `<!doctype html>
|
|
76
|
+
<html lang="en">
|
|
77
|
+
<head>
|
|
78
|
+
<meta charset="utf-8" />
|
|
79
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
80
|
+
<title>Actuate CMS — Configuration required</title>
|
|
81
|
+
<style>
|
|
82
|
+
:root { color-scheme: light dark; }
|
|
83
|
+
body { margin: 0; min-height: 100vh; display: grid; place-items: center;
|
|
84
|
+
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
85
|
+
background: #0b0d12; color: #e6e8eb; line-height: 1.5; padding: 24px; }
|
|
86
|
+
.card { max-width: 560px; width: 100%; background: #151922; border: 1px solid #262c38;
|
|
87
|
+
border-radius: 12px; padding: 28px 32px; }
|
|
88
|
+
h1 { font-size: 1.25rem; margin: 0 0 8px; font-weight: 500; }
|
|
89
|
+
p { margin: 0 0 12px; color: #aab2bd; }
|
|
90
|
+
code { background: #0b0d12; border: 1px solid #262c38; border-radius: 6px;
|
|
91
|
+
padding: 2px 6px; font-size: 0.9em; }
|
|
92
|
+
pre { background: #0b0d12; border: 1px solid #262c38; border-radius: 8px;
|
|
93
|
+
padding: 12px 14px; overflow-x: auto; font-size: 0.85rem; color: #e6e8eb; }
|
|
94
|
+
ol { margin: 0 0 4px; padding-left: 20px; color: #aab2bd; }
|
|
95
|
+
li { margin-bottom: 6px; }
|
|
96
|
+
</style>
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<main class="card">
|
|
100
|
+
<h1>Actuate CMS isn't configured yet</h1>
|
|
101
|
+
<p>The <code>CMS_SECRET</code> environment variable is missing or empty. It's
|
|
102
|
+
required to sign admin sessions, so the admin panel can't start without it.</p>
|
|
103
|
+
<ol>
|
|
104
|
+
<li>Generate a secret (at least 32 characters):
|
|
105
|
+
<pre>node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"</pre>
|
|
106
|
+
</li>
|
|
107
|
+
<li>Set <code>CMS_SECRET</code> to that value in your environment (or your
|
|
108
|
+
host's environment variables), then restart / redeploy.</li>
|
|
109
|
+
</ol>
|
|
110
|
+
<p>This page is only visible to operators — visitors never see it.</p>
|
|
111
|
+
</main>
|
|
112
|
+
</body>
|
|
113
|
+
</html>`;
|
|
114
|
+
return new Response(html, {
|
|
115
|
+
status: 503,
|
|
116
|
+
headers: {
|
|
117
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
118
|
+
'Cache-Control': 'no-store',
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
63
122
|
/**
|
|
64
123
|
* Match a pathname against a route prefix on a path-segment boundary, so the
|
|
65
124
|
* admin guard for `/admin` does not also capture sibling public routes like
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAYnE,MAAM,UAAU,mBAAmB,CAAC,SAAkC;IACpE,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,IAAI,QAAQ,CAAA;IACnD,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,EAAE,WAAW,CAAA;IAChD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAA;IAE3C,OAAO,KAAK,UAAU,aAAa,CAAC,OAAgB;QAClD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAEhC,MAAM,YAAY,GAAG,iBAAiB,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;QAC/D,MAAM,aAAa,GAAG,iBAAiB,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;QAEjE,IAAI,CAAC,YAAY,IAAI,CAAC,aAAa,EAAE,CAAC;YACpC,OAAO,IAAI,CAAA;QACb,CAAC;QAED,0EAA0E;QAC1E,uEAAuE;QACvE,wEAAwE;QACxE,iCAAiC;QACjC,MAAM,YAAY,GAChB,GAAG,CAAC,QAAQ,KAAK,GAAG,SAAS,QAAQ;YACrC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,SAAS,SAAS,CAAC;YAC9C,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAA;QAC3C,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO,IAAI,CAAA;QACb,CAAC;QAED,uEAAuE;QACvE,yEAAyE;QACzE,iEAAiE;QACjE,4CAA4C;QAC5C,IAAI,WAAW,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,aAAa,CAAC,EAAE,CAAC;YAC7E,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;YACrC,kEAAkE;YAClE,mCAAmC;YACnC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC;gBACnE,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;YACnD,CAAC;QACH,CAAC;QAED,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5B,OAAO,
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
package/dist/setup/index.d.ts
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
export interface SetupStatus {
|
|
9
9
|
setupRequired: boolean;
|
|
10
10
|
userCount: number;
|
|
11
|
+
/**
|
|
12
|
+
* True when the database is reachable but the Actuate CMS tables don't exist
|
|
13
|
+
* yet (migrations have not been applied). Distinct from `setupRequired`: the
|
|
14
|
+
* fix is `prisma migrate deploy`, not the setup wizard.
|
|
15
|
+
*/
|
|
16
|
+
migrationsRequired?: boolean;
|
|
11
17
|
}
|
|
12
18
|
export interface CreateAdminInput {
|
|
13
19
|
email: string;
|
|
@@ -30,6 +36,14 @@ export interface SetupResult {
|
|
|
30
36
|
* see the real cause (e.g. `ECONNREFUSED`, Prisma `P1001`).
|
|
31
37
|
*/
|
|
32
38
|
export declare function isDatabaseUnavailableError(err: unknown): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Detect errors that mean the database is reachable but the Actuate CMS tables
|
|
41
|
+
* have not been created yet (migrations not applied). This is a distinct,
|
|
42
|
+
* recoverable state from "empty database" — surfacing it lets the UI tell the
|
|
43
|
+
* operator to run migrations instead of dropping them on the login screen or a
|
|
44
|
+
* wizard that will fail on the first write.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isSchemaMissingError(err: unknown): boolean;
|
|
33
47
|
/**
|
|
34
48
|
* Check if the CMS needs first-run setup (no admin users exist).
|
|
35
49
|
* Uses a count query to avoid loading the full user model.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/setup/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/setup/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CA4BhE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAa1D;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAYtE;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAwC/F;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAgBxE"}
|
package/dist/setup/index.js
CHANGED
|
@@ -46,6 +46,27 @@ export function isDatabaseUnavailableError(err) {
|
|
|
46
46
|
}
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Detect errors that mean the database is reachable but the Actuate CMS tables
|
|
51
|
+
* have not been created yet (migrations not applied). This is a distinct,
|
|
52
|
+
* recoverable state from "empty database" — surfacing it lets the UI tell the
|
|
53
|
+
* operator to run migrations instead of dropping them on the login screen or a
|
|
54
|
+
* wizard that will fail on the first write.
|
|
55
|
+
*/
|
|
56
|
+
export function isSchemaMissingError(err) {
|
|
57
|
+
if (!err || typeof err !== 'object')
|
|
58
|
+
return false;
|
|
59
|
+
const code = err.code;
|
|
60
|
+
// Prisma P2021: table does not exist. P2022: column does not exist.
|
|
61
|
+
if (code === 'P2021' || code === 'P2022')
|
|
62
|
+
return true;
|
|
63
|
+
const message = err.message;
|
|
64
|
+
if (typeof message === 'string') {
|
|
65
|
+
return (message.includes('does not exist in the current database') ||
|
|
66
|
+
/relation "[^"]+" does not exist/i.test(message));
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
49
70
|
/**
|
|
50
71
|
* Check if the CMS needs first-run setup (no admin users exist).
|
|
51
72
|
* Uses a count query to avoid loading the full user model.
|
|
@@ -62,6 +83,10 @@ export async function checkSetupRequired(db) {
|
|
|
62
83
|
catch (err) {
|
|
63
84
|
if (isDatabaseUnavailableError(err))
|
|
64
85
|
throw err;
|
|
86
|
+
// Reachable DB but no tables yet → operator must run migrations.
|
|
87
|
+
if (isSchemaMissingError(err)) {
|
|
88
|
+
return { setupRequired: false, userCount: 0, migrationsRequired: true };
|
|
89
|
+
}
|
|
65
90
|
return { setupRequired: true, userCount: 0 };
|
|
66
91
|
}
|
|
67
92
|
}
|
|
@@ -98,9 +123,10 @@ export async function createInitialAdmin(db, input) {
|
|
|
98
123
|
return { success: true, userId: user.id };
|
|
99
124
|
}
|
|
100
125
|
catch (err) {
|
|
101
|
-
// A DB outage is an infrastructure failure, not a
|
|
102
|
-
// rethrow so the caller can return a 5xx
|
|
103
|
-
|
|
126
|
+
// A DB outage or an unmigrated schema is an infrastructure failure, not a
|
|
127
|
+
// bad request — rethrow so the caller can return a 5xx with an actionable
|
|
128
|
+
// message instead of a confusing 400.
|
|
129
|
+
if (isDatabaseUnavailableError(err) || isSchemaMissingError(err))
|
|
104
130
|
throw err;
|
|
105
131
|
const message = err instanceof Error ? err.message : 'Setup failed';
|
|
106
132
|
return { success: false, error: message };
|
package/dist/setup/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/setup/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/setup/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAyBlD;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CAAC,GAAY;IACrD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACjD,MAAM,IAAI,GAAI,GAA0B,CAAC,IAAI,CAAA;IAC7C,MAAM,IAAI,GAAI,GAA0B,CAAC,IAAI,CAAA;IAC7C,oEAAoE;IACpE,8DAA8D;IAC9D,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;QAC9B,OAAO;QACP,OAAO;QACP,OAAO;QACP,OAAO;QACP,OAAO;QACP,OAAO;QACP,cAAc;QACd,WAAW;QACX,WAAW;KACZ,CAAC,CAAA;IACF,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACtE,IAAI,IAAI,KAAK,iCAAiC;QAAE,OAAO,IAAI,CAAA;IAC3D,MAAM,OAAO,GAAI,GAA6B,CAAC,OAAO,CAAA;IACtD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,CACL,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;YAChC,OAAO,CAAC,QAAQ,CAAC,6BAA6B,CAAC;YAC/C,OAAO,CAAC,QAAQ,CAAC,wCAAwC,CAAC,CAC3D,CAAA;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACjD,MAAM,IAAI,GAAI,GAA0B,CAAC,IAAI,CAAA;IAC7C,oEAAoE;IACpE,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAA;IACrD,MAAM,OAAO,GAAI,GAA6B,CAAC,OAAO,CAAA;IACtD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,CACL,OAAO,CAAC,QAAQ,CAAC,wCAAwC,CAAC;YAC1D,kCAAkC,CAAC,IAAI,CAAC,OAAO,CAAC,CACjD,CAAA;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,EAAO;IAC9C,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;QACvC,OAAO,EAAE,aAAa,EAAE,SAAS,KAAK,CAAC,EAAE,SAAS,EAAE,CAAA;IACtD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,0BAA0B,CAAC,GAAG,CAAC;YAAE,MAAM,GAAG,CAAA;QAC9C,iEAAiE;QACjE,IAAI,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAA;QACzE,CAAC;QACD,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,CAAA;IAC9C,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,EAAO,EAAE,KAAuB;IACvE,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACnD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,wCAAwC,EAAE,CAAA;IAC5E,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAC/B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAA;IAC7E,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAEvD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,EAAO,EAAE,EAAE;YACnD,MAAM,aAAa,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;YAC3C,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;YAC1D,CAAC;YAED,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC;gBACpB,IAAI,EAAE;oBACJ,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE;oBACvC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE;oBACvB,YAAY;oBACZ,IAAI,EAAE,OAAO;oBACb,QAAQ,EAAE,IAAI;oBACd,UAAU,EAAE,IAAI;oBAChB,aAAa,EAAE,IAAI;iBACpB;aACF,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QAEF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAA;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,0EAA0E;QAC1E,0EAA0E;QAC1E,sCAAsC;QACtC,IAAI,0BAA0B,CAAC,GAAG,CAAC,IAAI,oBAAoB,CAAC,GAAG,CAAC;YAAE,MAAM,GAAG,CAAA;QAC3E,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAA;QACnE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;IAC3C,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAO;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IACzC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAA;IAE/C,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,kBAAkB,CAAC,EAAE,CAAC,CAAA;IACtD,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,CAAA;IAElD,OAAO,kBAAkB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;AAC1D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuate-media/cms-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/actuate-media/actuatecms.git",
|
|
@@ -64,6 +64,16 @@
|
|
|
64
64
|
"import": "./dist/security/center/index.js",
|
|
65
65
|
"default": "./dist/security/center/index.js"
|
|
66
66
|
},
|
|
67
|
+
"./email-sender": {
|
|
68
|
+
"types": "./dist/email/sender.d.ts",
|
|
69
|
+
"import": "./dist/email/sender.js",
|
|
70
|
+
"default": "./dist/email/sender.js"
|
|
71
|
+
},
|
|
72
|
+
"./roles": {
|
|
73
|
+
"types": "./dist/auth/roles.d.ts",
|
|
74
|
+
"import": "./dist/auth/roles.js",
|
|
75
|
+
"default": "./dist/auth/roles.js"
|
|
76
|
+
},
|
|
67
77
|
"./page-builder": {
|
|
68
78
|
"types": "./dist/page-builder/index.d.ts",
|
|
69
79
|
"import": "./dist/page-builder/index.js",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- AlterEnum
|
|
2
|
+
-- Add OWNER and VIEWER to the UserRole enum. These are appended (Postgres
|
|
3
|
+
-- ALTER TYPE ... ADD VALUE appends), and must be committed in their own
|
|
4
|
+
-- migration before any later statement references the new values.
|
|
5
|
+
ALTER TYPE "UserRole" ADD VALUE IF NOT EXISTS 'OWNER';
|
|
6
|
+
ALTER TYPE "UserRole" ADD VALUE IF NOT EXISTS 'VIEWER';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "InviteStatus" AS ENUM ('PENDING', 'ACCEPTED', 'EXPIRED', 'CANCELLED');
|
|
3
|
+
|
|
4
|
+
-- AlterTable: access-control lifecycle columns (all additive, nullable/defaulted)
|
|
5
|
+
ALTER TABLE "actuate_users" ADD COLUMN "suspendedAt" TIMESTAMP(3);
|
|
6
|
+
ALTER TABLE "actuate_users" ADD COLUMN "lockedAt" TIMESTAMP(3);
|
|
7
|
+
ALTER TABLE "actuate_users" ADD COLUMN "forcePasswordReset" BOOLEAN NOT NULL DEFAULT false;
|
|
8
|
+
ALTER TABLE "actuate_users" ADD COLUMN "mfaRequired" BOOLEAN NOT NULL DEFAULT false;
|
|
9
|
+
ALTER TABLE "actuate_users" ADD COLUMN "failedLoginCount" INTEGER NOT NULL DEFAULT 0;
|
|
10
|
+
ALTER TABLE "actuate_users" ADD COLUMN "lastActiveAt" TIMESTAMP(3);
|
|
11
|
+
ALTER TABLE "actuate_users" ADD COLUMN "invitedById" TEXT;
|
|
12
|
+
ALTER TABLE "actuate_users" ADD COLUMN "acceptedInviteAt" TIMESTAMP(3);
|
|
13
|
+
|
|
14
|
+
-- CreateTable
|
|
15
|
+
CREATE TABLE "actuate_invites" (
|
|
16
|
+
"id" TEXT NOT NULL,
|
|
17
|
+
"email" TEXT NOT NULL,
|
|
18
|
+
"role" "UserRole" NOT NULL,
|
|
19
|
+
"status" "InviteStatus" NOT NULL DEFAULT 'PENDING',
|
|
20
|
+
"tokenHash" TEXT NOT NULL,
|
|
21
|
+
"message" TEXT,
|
|
22
|
+
"invitedByUserId" TEXT,
|
|
23
|
+
"acceptedUserId" TEXT,
|
|
24
|
+
"expiresAt" TIMESTAMP(3) NOT NULL,
|
|
25
|
+
"acceptedAt" TIMESTAMP(3),
|
|
26
|
+
"cancelledAt" TIMESTAMP(3),
|
|
27
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
28
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
29
|
+
|
|
30
|
+
CONSTRAINT "actuate_invites_pkey" PRIMARY KEY ("id")
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
-- CreateIndex
|
|
34
|
+
CREATE INDEX "actuate_invites_email_idx" ON "actuate_invites"("email");
|
|
35
|
+
|
|
36
|
+
-- CreateIndex
|
|
37
|
+
CREATE INDEX "actuate_invites_tokenHash_idx" ON "actuate_invites"("tokenHash");
|
|
38
|
+
|
|
39
|
+
-- CreateIndex
|
|
40
|
+
CREATE INDEX "actuate_invites_status_idx" ON "actuate_invites"("status");
|
package/prisma/schema.prisma
CHANGED
|
@@ -37,6 +37,15 @@ enum UserRole {
|
|
|
37
37
|
EDITOR
|
|
38
38
|
AUTHOR
|
|
39
39
|
CLIENT
|
|
40
|
+
OWNER
|
|
41
|
+
VIEWER
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
enum InviteStatus {
|
|
45
|
+
PENDING
|
|
46
|
+
ACCEPTED
|
|
47
|
+
EXPIRED
|
|
48
|
+
CANCELLED
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
enum ChangeType {
|
|
@@ -163,6 +172,17 @@ model User {
|
|
|
163
172
|
createdAt DateTime @default(now())
|
|
164
173
|
updatedAt DateTime @updatedAt
|
|
165
174
|
|
|
175
|
+
// Access-control lifecycle (Users management subsystem). All additive and
|
|
176
|
+
// nullable/defaulted so existing rows migrate cleanly.
|
|
177
|
+
suspendedAt DateTime?
|
|
178
|
+
lockedAt DateTime?
|
|
179
|
+
forcePasswordReset Boolean @default(false)
|
|
180
|
+
mfaRequired Boolean @default(false)
|
|
181
|
+
failedLoginCount Int @default(0)
|
|
182
|
+
lastActiveAt DateTime?
|
|
183
|
+
invitedById String?
|
|
184
|
+
acceptedInviteAt DateTime?
|
|
185
|
+
|
|
166
186
|
sessions Session[]
|
|
167
187
|
oauthAccounts OAuthAccount[]
|
|
168
188
|
apiKeys ApiKey[]
|
|
@@ -805,6 +825,29 @@ model PasswordResetToken {
|
|
|
805
825
|
@@map("actuate_password_reset_tokens")
|
|
806
826
|
}
|
|
807
827
|
|
|
828
|
+
model Invite {
|
|
829
|
+
id String @id @default(cuid())
|
|
830
|
+
email String
|
|
831
|
+
role UserRole
|
|
832
|
+
status InviteStatus @default(PENDING)
|
|
833
|
+
tokenHash String
|
|
834
|
+
message String?
|
|
835
|
+
// Stored as a bare id (not an FK relation) to mirror AuditLog: an inviter
|
|
836
|
+
// being removed must never cascade-delete the invite history.
|
|
837
|
+
invitedByUserId String?
|
|
838
|
+
acceptedUserId String?
|
|
839
|
+
expiresAt DateTime
|
|
840
|
+
acceptedAt DateTime?
|
|
841
|
+
cancelledAt DateTime?
|
|
842
|
+
createdAt DateTime @default(now())
|
|
843
|
+
updatedAt DateTime @updatedAt
|
|
844
|
+
|
|
845
|
+
@@index([email])
|
|
846
|
+
@@index([tokenHash])
|
|
847
|
+
@@index([status])
|
|
848
|
+
@@map("actuate_invites")
|
|
849
|
+
}
|
|
850
|
+
|
|
808
851
|
model ScriptTag {
|
|
809
852
|
id String @id @default(cuid())
|
|
810
853
|
name String
|