@aphexcms/cms-core 9.8.1 → 9.10.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/api/organizations.d.ts +2 -1
- package/dist/api/organizations.d.ts.map +1 -1
- package/dist/api/organizations.js +2 -1
- package/dist/api/schemas/documents.d.ts +1 -0
- package/dist/api/schemas/documents.d.ts.map +1 -1
- package/dist/api/schemas/documents.js +1 -0
- package/dist/api/schemas/jobs.d.ts +15 -0
- package/dist/api/schemas/jobs.d.ts.map +1 -0
- package/dist/api/schemas/jobs.js +17 -0
- package/dist/api/schemas/user.d.ts.map +1 -1
- package/dist/api/schemas/user.js +6 -2
- package/dist/auth/account-deletion.d.ts +35 -0
- package/dist/auth/account-deletion.d.ts.map +1 -0
- package/dist/auth/account-deletion.js +65 -0
- package/dist/auth/bootstrap.d.ts +104 -0
- package/dist/auth/bootstrap.d.ts.map +1 -0
- package/dist/auth/bootstrap.js +164 -0
- package/dist/auth/instance-state.d.ts +13 -0
- package/dist/auth/instance-state.d.ts.map +1 -0
- package/dist/auth/instance-state.js +30 -0
- package/dist/auth/invitation-status.d.ts +25 -0
- package/dist/auth/invitation-status.d.ts.map +1 -0
- package/dist/auth/invitation-status.js +41 -0
- package/dist/auth/sign-up-policy.d.ts +62 -0
- package/dist/auth/sign-up-policy.d.ts.map +1 -0
- package/dist/auth/sign-up-policy.js +120 -0
- package/dist/components/AdminApp.svelte +83 -6
- package/dist/components/AdminApp.svelte.d.ts.map +1 -1
- package/dist/components/admin/ActivityView.svelte +263 -13
- package/dist/components/admin/ActivityView.svelte.d.ts +10 -1
- package/dist/components/admin/ActivityView.svelte.d.ts.map +1 -1
- package/dist/db/interfaces/document.d.ts +9 -0
- package/dist/db/interfaces/document.d.ts.map +1 -1
- package/dist/db/interfaces/events.d.ts +29 -2
- package/dist/db/interfaces/events.d.ts.map +1 -1
- package/dist/db/interfaces/index.d.ts +1 -0
- package/dist/db/interfaces/index.d.ts.map +1 -1
- package/dist/db/interfaces/index.js +1 -0
- package/dist/db/interfaces/instance.d.ts +29 -0
- package/dist/db/interfaces/instance.d.ts.map +1 -1
- package/dist/db/interfaces/instance.js +8 -1
- package/dist/events/built-in-consumers.d.ts +20 -0
- package/dist/events/built-in-consumers.d.ts.map +1 -0
- package/dist/events/built-in-consumers.js +49 -0
- package/dist/events/catalog.d.ts +16 -0
- package/dist/events/catalog.d.ts.map +1 -1
- package/dist/events/catalog.js +17 -0
- package/dist/events/consumer.d.ts +14 -0
- package/dist/events/consumer.d.ts.map +1 -1
- package/dist/events/consumer.js +1 -0
- package/dist/events/emit.d.ts +23 -0
- package/dist/events/emit.d.ts.map +1 -1
- package/dist/events/emit.js +32 -1
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +2 -0
- package/dist/field-validation/utils.d.ts +27 -4
- package/dist/field-validation/utils.d.ts.map +1 -1
- package/dist/field-validation/utils.js +78 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/jobs/run-batch.d.ts +7 -1
- package/dist/jobs/run-batch.d.ts.map +1 -1
- package/dist/jobs/run-batch.js +3 -1
- package/dist/lib/api/organizations.d.ts +2 -1
- package/dist/lib/api/organizations.d.ts.map +1 -1
- package/dist/lib/api/organizations.js +2 -1
- package/dist/lib/api/organizations.js.map +1 -1
- package/dist/lib/api/schemas/api-keys.d.ts +4 -4
- package/dist/lib/api/schemas/documents.d.ts +1 -0
- package/dist/lib/api/schemas/documents.d.ts.map +1 -1
- package/dist/lib/api/schemas/documents.js +1 -0
- package/dist/lib/api/schemas/documents.js.map +1 -1
- package/dist/lib/api/schemas/jobs.d.ts +15 -0
- package/dist/lib/api/schemas/jobs.d.ts.map +1 -0
- package/dist/lib/api/schemas/jobs.js +18 -0
- package/dist/lib/api/schemas/jobs.js.map +1 -0
- package/dist/lib/api/schemas/user.d.ts.map +1 -1
- package/dist/lib/api/schemas/user.js +6 -2
- package/dist/lib/api/schemas/user.js.map +1 -1
- package/dist/lib/auth/account-deletion.d.ts +35 -0
- package/dist/lib/auth/account-deletion.d.ts.map +1 -0
- package/dist/lib/auth/account-deletion.js +66 -0
- package/dist/lib/auth/account-deletion.js.map +1 -0
- package/dist/lib/auth/bootstrap.d.ts +104 -0
- package/dist/lib/auth/bootstrap.d.ts.map +1 -0
- package/dist/lib/auth/bootstrap.js +165 -0
- package/dist/lib/auth/bootstrap.js.map +1 -0
- package/dist/lib/auth/instance-state.d.ts +13 -0
- package/dist/lib/auth/instance-state.d.ts.map +1 -0
- package/dist/lib/auth/instance-state.js +31 -0
- package/dist/lib/auth/instance-state.js.map +1 -0
- package/dist/lib/auth/invitation-status.d.ts +25 -0
- package/dist/lib/auth/invitation-status.d.ts.map +1 -0
- package/dist/lib/auth/invitation-status.js +42 -0
- package/dist/lib/auth/invitation-status.js.map +1 -0
- package/dist/lib/auth/sign-up-policy.d.ts +62 -0
- package/dist/lib/auth/sign-up-policy.d.ts.map +1 -0
- package/dist/lib/auth/sign-up-policy.js +121 -0
- package/dist/lib/auth/sign-up-policy.js.map +1 -0
- package/dist/lib/db/interfaces/document.d.ts +9 -0
- package/dist/lib/db/interfaces/document.d.ts.map +1 -1
- package/dist/lib/db/interfaces/events.d.ts +29 -2
- package/dist/lib/db/interfaces/events.d.ts.map +1 -1
- package/dist/lib/db/interfaces/index.d.ts +1 -0
- package/dist/lib/db/interfaces/index.d.ts.map +1 -1
- package/dist/lib/db/interfaces/index.js +1 -0
- package/dist/lib/db/interfaces/index.js.map +1 -1
- package/dist/lib/db/interfaces/instance.d.ts +29 -0
- package/dist/lib/db/interfaces/instance.d.ts.map +1 -1
- package/dist/lib/db/interfaces/instance.js +8 -1
- package/dist/lib/db/interfaces/instance.js.map +1 -1
- package/dist/lib/events/built-in-consumers.d.ts +20 -0
- package/dist/lib/events/built-in-consumers.d.ts.map +1 -0
- package/dist/lib/events/built-in-consumers.js +50 -0
- package/dist/lib/events/built-in-consumers.js.map +1 -0
- package/dist/lib/events/catalog.d.ts +16 -0
- package/dist/lib/events/catalog.d.ts.map +1 -1
- package/dist/lib/events/catalog.js +17 -0
- package/dist/lib/events/catalog.js.map +1 -1
- package/dist/lib/events/consumer.d.ts +14 -0
- package/dist/lib/events/consumer.d.ts.map +1 -1
- package/dist/lib/events/consumer.js +1 -0
- package/dist/lib/events/consumer.js.map +1 -1
- package/dist/lib/events/emit.d.ts +23 -0
- package/dist/lib/events/emit.d.ts.map +1 -1
- package/dist/lib/events/emit.js +32 -1
- package/dist/lib/events/emit.js.map +1 -1
- package/dist/lib/events/index.d.ts +2 -0
- package/dist/lib/events/index.d.ts.map +1 -1
- package/dist/lib/events/index.js +2 -0
- package/dist/lib/events/index.js.map +1 -1
- package/dist/lib/field-validation/utils.d.ts +27 -4
- package/dist/lib/field-validation/utils.d.ts.map +1 -1
- package/dist/lib/field-validation/utils.js +78 -10
- package/dist/lib/field-validation/utils.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +3 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/jobs/run-batch.d.ts +7 -1
- package/dist/lib/jobs/run-batch.d.ts.map +1 -1
- package/dist/lib/jobs/run-batch.js +3 -1
- package/dist/lib/jobs/run-batch.js.map +1 -1
- package/dist/lib/local-api/collection-api.d.ts +48 -1
- package/dist/lib/local-api/collection-api.d.ts.map +1 -1
- package/dist/lib/local-api/collection-api.js +110 -11
- package/dist/lib/local-api/collection-api.js.map +1 -1
- package/dist/lib/local-api/index.d.ts +1 -1
- package/dist/lib/local-api/index.d.ts.map +1 -1
- package/dist/lib/local-api/index.js +1 -1
- package/dist/lib/local-api/index.js.map +1 -1
- package/dist/lib/plugins/resolver.d.ts.map +1 -1
- package/dist/lib/plugins/resolver.js +7 -1
- package/dist/lib/plugins/resolver.js.map +1 -1
- package/dist/lib/schema-utils/utils.d.ts +31 -1
- package/dist/lib/schema-utils/utils.d.ts.map +1 -1
- package/dist/lib/schema-utils/utils.js +68 -0
- package/dist/lib/schema-utils/utils.js.map +1 -1
- package/dist/lib/server/api/rate-limit.d.ts +38 -0
- package/dist/lib/server/api/rate-limit.d.ts.map +1 -0
- package/dist/lib/server/api/rate-limit.js +71 -0
- package/dist/lib/server/api/rate-limit.js.map +1 -0
- package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
- package/dist/lib/server/api/routes/documents-by-id.js +10 -1
- package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
- package/dist/lib/server/api/routes/documents-publish.d.ts.map +1 -1
- package/dist/lib/server/api/routes/documents-publish.js +10 -0
- package/dist/lib/server/api/routes/documents-publish.js.map +1 -1
- package/dist/lib/server/api/routes/documents.d.ts.map +1 -1
- package/dist/lib/server/api/routes/documents.js +11 -0
- package/dist/lib/server/api/routes/documents.js.map +1 -1
- package/dist/lib/server/api/routes/jobs.d.ts.map +1 -1
- package/dist/lib/server/api/routes/jobs.js +174 -20
- package/dist/lib/server/api/routes/jobs.js.map +1 -1
- package/dist/lib/server/api/routes/organizations-by-id.d.ts.map +1 -1
- package/dist/lib/server/api/routes/organizations-by-id.js +61 -3
- package/dist/lib/server/api/routes/organizations-by-id.js.map +1 -1
- package/dist/lib/server/api/routes/organizations-invitations.d.ts.map +1 -1
- package/dist/lib/server/api/routes/organizations-invitations.js +31 -2
- package/dist/lib/server/api/routes/organizations-invitations.js.map +1 -1
- package/dist/lib/server/api/routes/resolve-organization-names.d.ts +17 -0
- package/dist/lib/server/api/routes/resolve-organization-names.d.ts.map +1 -0
- package/dist/lib/server/api/routes/resolve-organization-names.js +26 -0
- package/dist/lib/server/api/routes/resolve-organization-names.js.map +1 -0
- package/dist/lib/server/api/routes/user.d.ts +0 -8
- package/dist/lib/server/api/routes/user.d.ts.map +1 -1
- package/dist/lib/server/api/routes/user.js +144 -0
- package/dist/lib/server/api/routes/user.js.map +1 -1
- package/dist/lib/server/index.d.ts +6 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js +9 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/type-gen.d.ts.map +1 -1
- package/dist/lib/type-gen.js +11 -5
- package/dist/lib/type-gen.js.map +1 -1
- package/dist/lib/types/capabilities.d.ts +14 -0
- package/dist/lib/types/capabilities.d.ts.map +1 -1
- package/dist/lib/types/capabilities.js +37 -18
- package/dist/lib/types/capabilities.js.map +1 -1
- package/dist/lib/types/events.d.ts +27 -4
- package/dist/lib/types/events.d.ts.map +1 -1
- package/dist/lib/types/filters.d.ts +31 -0
- package/dist/lib/types/filters.d.ts.map +1 -1
- package/dist/lib/types/schemas.d.ts +14 -0
- package/dist/lib/types/schemas.d.ts.map +1 -1
- package/dist/lib/utils/fetch-remote-file.d.ts.map +1 -1
- package/dist/lib/utils/fetch-remote-file.js +32 -3
- package/dist/lib/utils/fetch-remote-file.js.map +1 -1
- package/dist/local-api/collection-api.d.ts +48 -1
- package/dist/local-api/collection-api.d.ts.map +1 -1
- package/dist/local-api/collection-api.js +110 -11
- package/dist/local-api/index.d.ts +1 -1
- package/dist/local-api/index.d.ts.map +1 -1
- package/dist/local-api/index.js +1 -1
- package/dist/plugins/resolver.d.ts.map +1 -1
- package/dist/plugins/resolver.js +7 -1
- package/dist/schema-utils/utils.d.ts +31 -1
- package/dist/schema-utils/utils.d.ts.map +1 -1
- package/dist/schema-utils/utils.js +68 -0
- package/dist/server/api/rate-limit.d.ts +38 -0
- package/dist/server/api/rate-limit.d.ts.map +1 -0
- package/dist/server/api/rate-limit.js +70 -0
- package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
- package/dist/server/api/routes/documents-by-id.js +10 -1
- package/dist/server/api/routes/documents-publish.d.ts.map +1 -1
- package/dist/server/api/routes/documents-publish.js +10 -0
- package/dist/server/api/routes/documents.d.ts.map +1 -1
- package/dist/server/api/routes/documents.js +11 -0
- package/dist/server/api/routes/jobs.d.ts.map +1 -1
- package/dist/server/api/routes/jobs.js +174 -20
- package/dist/server/api/routes/organizations-by-id.d.ts.map +1 -1
- package/dist/server/api/routes/organizations-by-id.js +61 -3
- package/dist/server/api/routes/organizations-invitations.d.ts.map +1 -1
- package/dist/server/api/routes/organizations-invitations.js +31 -2
- package/dist/server/api/routes/resolve-organization-names.d.ts +17 -0
- package/dist/server/api/routes/resolve-organization-names.d.ts.map +1 -0
- package/dist/server/api/routes/resolve-organization-names.js +25 -0
- package/dist/server/api/routes/user.d.ts +0 -8
- package/dist/server/api/routes/user.d.ts.map +1 -1
- package/dist/server/api/routes/user.js +144 -0
- package/dist/server/index.d.ts +6 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +9 -1
- package/dist/type-gen.d.ts.map +1 -1
- package/dist/type-gen.js +11 -5
- package/dist/types/capabilities.d.ts +14 -0
- package/dist/types/capabilities.d.ts.map +1 -1
- package/dist/types/capabilities.js +37 -18
- package/dist/types/events.d.ts +27 -4
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/filters.d.ts +31 -0
- package/dist/types/filters.d.ts.map +1 -1
- package/dist/types/schemas.d.ts +14 -0
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/utils/fetch-remote-file.d.ts.map +1 -1
- package/dist/utils/fetch-remote-file.js +32 -3
- package/package.json +6 -6
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { BootstrapPolicy, InstanceRole } from './bootstrap.js';
|
|
2
|
+
import type { DatabaseAdapter } from '../db/interfaces/index.js';
|
|
3
|
+
import type { UserProfile } from '../types/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Thrown when sign-up is refused by policy.
|
|
6
|
+
*
|
|
7
|
+
* Providers translate this into whatever their transport expects — Better Auth
|
|
8
|
+
* raises an `APIError`, a REST handler might return 403. The message is written
|
|
9
|
+
* to be safe to show a user.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SignUpBlockedError extends Error {
|
|
12
|
+
readonly code = "SIGN_UP_BLOCKED";
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
export interface SignUpGateOptions {
|
|
16
|
+
db: DatabaseAdapter;
|
|
17
|
+
/** Address attempting to register. Matched case-insensitively. */
|
|
18
|
+
email: string | undefined;
|
|
19
|
+
/** When false the gate is disabled entirely and this is a no-op. */
|
|
20
|
+
inviteOnly: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Throws `SignUpBlockedError` unless this address may create an account.
|
|
24
|
+
*
|
|
25
|
+
* Two ways through: the instance is provably empty (nobody exists yet to have
|
|
26
|
+
* sent an invitation, so gating the first sign-up would lock the door with the
|
|
27
|
+
* keys inside), or the address holds a pending invitation.
|
|
28
|
+
*
|
|
29
|
+
* Narrowing that first exception is the bootstrap policy's job — see
|
|
30
|
+
* `resolveBootstrapRole` — not this gate's.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertSignUpAllowed({ db, email, inviteOnly }: SignUpGateOptions): Promise<void>;
|
|
33
|
+
export interface BootstrapRoleOptions {
|
|
34
|
+
db: DatabaseAdapter;
|
|
35
|
+
user: {
|
|
36
|
+
id: string;
|
|
37
|
+
email: string;
|
|
38
|
+
emailVerified: boolean;
|
|
39
|
+
};
|
|
40
|
+
/** The request that triggered profile creation, when there is one. */
|
|
41
|
+
request?: Request;
|
|
42
|
+
bootstrap: BootstrapPolicy;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Runs the bootstrap policy for a brand-new user, returning the instance role to
|
|
46
|
+
* grant or `null` for none.
|
|
47
|
+
*
|
|
48
|
+
* Deliberately not wrapped in a transaction. SQLite allows a single writer, and
|
|
49
|
+
* holding that lock across the policy's own round-trips collides with the auth
|
|
50
|
+
* provider's concurrent user/session inserts (SQLITE_BUSY). The atomicity was
|
|
51
|
+
* only partial anyway — see `claimCode`: a claim is single-use, not mutually
|
|
52
|
+
* exclusive.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveBootstrapRole({ db, user, request, bootstrap }: BootstrapRoleOptions): Promise<InstanceRole | null>;
|
|
55
|
+
/**
|
|
56
|
+
* The one place a CMS user profile is created for a newly-authenticated user.
|
|
57
|
+
*
|
|
58
|
+
* Every `AuthProvider` should route new profiles through here so bootstrap
|
|
59
|
+
* promotion happens identically regardless of who did the authenticating.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createUserProfileWithBootstrap(options: BootstrapRoleOptions): Promise<UserProfile>;
|
|
62
|
+
//# sourceMappingURL=sign-up-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign-up-policy.d.ts","sourceRoot":"","sources":["../../src/lib/auth/sign-up-policy.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,qBAAqB;gBACtB,OAAO,EAAE,MAAM;CAI3B;AAED,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,eAAe,CAAC;IACpB,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,EACzC,EAAE,EACF,KAAK,EACL,UAAU,EACV,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBnC;AAED,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,eAAe,CAAC;IACpB,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5D,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,eAAe,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,EAC1C,EAAE,EACF,IAAI,EACJ,OAAO,EACP,SAAS,EACT,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAerD;AAuBD;;;;;GAKG;AACH,wBAAsB,8BAA8B,CACnD,OAAO,EAAE,oBAAoB,GAC3B,OAAO,CAAC,WAAW,CAAC,CA6BtB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Who may sign up, and what they get when they do.
|
|
2
|
+
//
|
|
3
|
+
// These are **CMS policy decisions, not auth-provider concerns**. Whether an
|
|
4
|
+
// instance is invite-only, and who becomes its first administrator, should mean
|
|
5
|
+
// the same thing whether the app authenticates with Better Auth, Keycloak,
|
|
6
|
+
// Supabase, or something hand-rolled — so they live behind the `AuthProvider`
|
|
7
|
+
// port rather than inside any one implementation of it.
|
|
8
|
+
//
|
|
9
|
+
// Before this existed, both rules were enforced inside `@aphexcms/auth`'s
|
|
10
|
+
// Better Auth wiring, which meant a third-party provider silently inherited
|
|
11
|
+
// neither: `inviteOnly: true` would be configured and quietly do nothing. An
|
|
12
|
+
// implementation is free to call these wherever its lifecycle allows, but it is
|
|
13
|
+
// no longer free to *re-derive* them.
|
|
14
|
+
import { cmsLogger } from '../utils/logger.js';
|
|
15
|
+
import { isInstanceEmpty, canDetermineInstanceEmptiness } from './instance-state.js';
|
|
16
|
+
import { isPendingInvitation } from './invitation-status.js';
|
|
17
|
+
/**
|
|
18
|
+
* Thrown when sign-up is refused by policy.
|
|
19
|
+
*
|
|
20
|
+
* Providers translate this into whatever their transport expects — Better Auth
|
|
21
|
+
* raises an `APIError`, a REST handler might return 403. The message is written
|
|
22
|
+
* to be safe to show a user.
|
|
23
|
+
*/
|
|
24
|
+
export class SignUpBlockedError extends Error {
|
|
25
|
+
code = 'SIGN_UP_BLOCKED';
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'SignUpBlockedError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Throws `SignUpBlockedError` unless this address may create an account.
|
|
33
|
+
*
|
|
34
|
+
* Two ways through: the instance is provably empty (nobody exists yet to have
|
|
35
|
+
* sent an invitation, so gating the first sign-up would lock the door with the
|
|
36
|
+
* keys inside), or the address holds a pending invitation.
|
|
37
|
+
*
|
|
38
|
+
* Narrowing that first exception is the bootstrap policy's job — see
|
|
39
|
+
* `resolveBootstrapRole` — not this gate's.
|
|
40
|
+
*/
|
|
41
|
+
export async function assertSignUpAllowed({ db, email, inviteOnly }) {
|
|
42
|
+
if (!inviteOnly)
|
|
43
|
+
return;
|
|
44
|
+
if (await isInstanceEmpty(db)) {
|
|
45
|
+
cmsLogger.info('[SignUp]', 'Allowing first sign-up on an empty instance');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Invitations are stored lower-cased and matched exactly, so a mixed-case
|
|
49
|
+
// sign-up would otherwise find nothing and be refused.
|
|
50
|
+
const address = email?.toLowerCase().trim();
|
|
51
|
+
const invitations = address ? await db.findInvitationsByEmail(address) : [];
|
|
52
|
+
if (!invitations.some((invitation) => isPendingInvitation(invitation))) {
|
|
53
|
+
cmsLogger.warn('[SignUp]', `Blocked sign-up for ${address} — no pending invitation`);
|
|
54
|
+
throw new SignUpBlockedError('Sign-up is by invitation only. Ask an administrator for an invite.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Runs the bootstrap policy for a brand-new user, returning the instance role to
|
|
59
|
+
* grant or `null` for none.
|
|
60
|
+
*
|
|
61
|
+
* Deliberately not wrapped in a transaction. SQLite allows a single writer, and
|
|
62
|
+
* holding that lock across the policy's own round-trips collides with the auth
|
|
63
|
+
* provider's concurrent user/session inserts (SQLITE_BUSY). The atomicity was
|
|
64
|
+
* only partial anyway — see `claimCode`: a claim is single-use, not mutually
|
|
65
|
+
* exclusive.
|
|
66
|
+
*/
|
|
67
|
+
export async function resolveBootstrapRole({ db, user, request, bootstrap }) {
|
|
68
|
+
// "Provably empty", not "couldn't prove otherwise". An adapter that can't
|
|
69
|
+
// answer this must not read as "no users exist" — that inversion once
|
|
70
|
+
// promoted every single sign-up to super admin.
|
|
71
|
+
const isFirstUser = await isInstanceEmpty(db);
|
|
72
|
+
if (!canDetermineInstanceEmptiness(db)) {
|
|
73
|
+
cmsLogger.warn('[SignUp]', 'Database adapter does not implement hasAnyUserProfiles() — skipping bootstrap. ' +
|
|
74
|
+
'Provision the first administrator out of band.');
|
|
75
|
+
}
|
|
76
|
+
return bootstrap({ user, isFirstUser, request, db });
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Take the one-time bootstrap claim, or report that somebody else has it.
|
|
80
|
+
*
|
|
81
|
+
* An adapter without `tryClaimBootstrap` keeps the old, racy behaviour rather
|
|
82
|
+
* than never promoting anyone — the opposite of the rule `isInstanceEmpty`
|
|
83
|
+
* follows, and on purpose. There, failing closed skips a promotion; here it
|
|
84
|
+
* would leave a fresh install with no administrator and no way to get one.
|
|
85
|
+
*/
|
|
86
|
+
async function claimBootstrapPromotion(db) {
|
|
87
|
+
const claim = db.tryClaimBootstrap?.bind(db);
|
|
88
|
+
if (!claim) {
|
|
89
|
+
cmsLogger.warn('[SignUp]', 'Database adapter does not implement tryClaimBootstrap() — bootstrap promotion is not ' +
|
|
90
|
+
'race-safe. Concurrent first sign-ups could both be promoted.');
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
return claim();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The one place a CMS user profile is created for a newly-authenticated user.
|
|
97
|
+
*
|
|
98
|
+
* Every `AuthProvider` should route new profiles through here so bootstrap
|
|
99
|
+
* promotion happens identically regardless of who did the authenticating.
|
|
100
|
+
*/
|
|
101
|
+
export async function createUserProfileWithBootstrap(options) {
|
|
102
|
+
const proposed = await resolveBootstrapRole(options);
|
|
103
|
+
// The policy decided *whether* this user qualifies; the claim decides whether
|
|
104
|
+
// they got there first. Both are needed: the policy's own `isFirstUser` check
|
|
105
|
+
// reads a snapshot taken before the profile insert, so two sign-ups racing on
|
|
106
|
+
// a fresh instance can both pass it and both be promoted. Every promotion in
|
|
107
|
+
// the instance's lifetime funnels through this one call, so claiming here
|
|
108
|
+
// covers every policy — including a custom one — without each having to know
|
|
109
|
+
// about the race.
|
|
110
|
+
const granted = proposed && (await claimBootstrapPromotion(options.db)) ? proposed : null;
|
|
111
|
+
if (proposed && !granted) {
|
|
112
|
+
cmsLogger.warn('[SignUp]', `Bootstrap promotion for ${options.user.id} lost the claim — another sign-up got there first. ` +
|
|
113
|
+
'Creating an ordinary profile instead.');
|
|
114
|
+
}
|
|
115
|
+
cmsLogger.info('[SignUp]', `Creating profile for ${options.user.id}${granted ? ` with role ${granted.toUpperCase()}` : ''}`);
|
|
116
|
+
return options.db.createUserProfile({
|
|
117
|
+
userId: options.user.id,
|
|
118
|
+
role: granted ?? 'editor'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Alert, AlertDescription, AlertTitle } from '@aphexcms/ui/shadcn/alert';
|
|
7
7
|
import { Button } from '@aphexcms/ui/shadcn/button';
|
|
8
|
+
import { Input } from '@aphexcms/ui/shadcn/input';
|
|
8
9
|
import { useSidebar } from '@aphexcms/ui/shadcn/sidebar';
|
|
9
10
|
import * as Tabs from '@aphexcms/ui/shadcn/tabs';
|
|
10
11
|
import * as Popover from '@aphexcms/ui/shadcn/popover';
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
import type { AdminArea } from '../admin/types';
|
|
19
20
|
import { setAdminNav } from '../admin/nav.svelte';
|
|
20
21
|
import type { Component } from 'svelte';
|
|
22
|
+
import { tick } from 'svelte';
|
|
21
23
|
import { setFieldComponents } from '../admin/field-components.svelte';
|
|
22
24
|
import { setBlockPreviews, type BlockPreviewProps } from '../admin/block-previews.svelte';
|
|
23
25
|
import AdminSlot from './admin/AdminSlot.svelte';
|
|
@@ -39,7 +41,9 @@
|
|
|
39
41
|
ArrowUp10,
|
|
40
42
|
ArrowDownUp,
|
|
41
43
|
ChevronLeft,
|
|
42
|
-
ChevronRight
|
|
44
|
+
ChevronRight,
|
|
45
|
+
Search,
|
|
46
|
+
X
|
|
43
47
|
} from '@lucide/svelte';
|
|
44
48
|
import type { Organization } from '../types/organization';
|
|
45
49
|
import { getOrderingsForSchema } from '../utils/default-orderings';
|
|
@@ -254,6 +258,20 @@
|
|
|
254
258
|
let docTotalDocs = $state(0);
|
|
255
259
|
let docPageSize = $state(20);
|
|
256
260
|
const PAGE_SIZE_OPTIONS = [20, 50, 100] as const;
|
|
261
|
+
let docSearchQuery = $state('');
|
|
262
|
+
let docSearchOpen = $state(false);
|
|
263
|
+
let docSearchInputEl = $state<HTMLInputElement | null>(null);
|
|
264
|
+
let docSearchTimeout: ReturnType<typeof setTimeout>;
|
|
265
|
+
|
|
266
|
+
function toggleDocSearch() {
|
|
267
|
+
if (docSearchOpen) {
|
|
268
|
+
docSearchOpen = false;
|
|
269
|
+
if (docSearchQuery) handleDocSearchInput('');
|
|
270
|
+
} else {
|
|
271
|
+
docSearchOpen = true;
|
|
272
|
+
tick().then(() => docSearchInputEl?.focus());
|
|
273
|
+
}
|
|
274
|
+
}
|
|
257
275
|
|
|
258
276
|
// Organizations lookup map for displaying org names
|
|
259
277
|
let organizationsMap = $state<Map<string, Organization>>(new Map());
|
|
@@ -617,6 +635,7 @@
|
|
|
617
635
|
editorStack = [];
|
|
618
636
|
if (selectedDocumentType !== docType) {
|
|
619
637
|
docCurrentPage = 1;
|
|
638
|
+
docSearchQuery = '';
|
|
620
639
|
selectedDocumentType = docType;
|
|
621
640
|
fetchDocuments(docType);
|
|
622
641
|
}
|
|
@@ -703,6 +722,7 @@
|
|
|
703
722
|
// Only fetch if docType changed (org changes are handled by separate effect)
|
|
704
723
|
if (selectedDocumentType !== docType) {
|
|
705
724
|
docCurrentPage = 1;
|
|
725
|
+
docSearchQuery = '';
|
|
706
726
|
selectedDocumentType = docType;
|
|
707
727
|
fetchDocuments(docType);
|
|
708
728
|
} else {
|
|
@@ -973,6 +993,15 @@
|
|
|
973
993
|
}
|
|
974
994
|
}
|
|
975
995
|
|
|
996
|
+
function handleDocSearchInput(value: string) {
|
|
997
|
+
docSearchQuery = value;
|
|
998
|
+
clearTimeout(docSearchTimeout);
|
|
999
|
+
docSearchTimeout = setTimeout(() => {
|
|
1000
|
+
docCurrentPage = 1;
|
|
1001
|
+
if (selectedDocumentType) fetchDocuments(selectedDocumentType);
|
|
1002
|
+
}, 300);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
976
1005
|
async function fetchDocuments(docType: string) {
|
|
977
1006
|
// No type = nothing to list (e.g. mid-navigation from a plugin tab before a
|
|
978
1007
|
// type is selected). Bail quietly rather than hitting the API and toasting
|
|
@@ -988,7 +1017,8 @@
|
|
|
988
1017
|
page: docCurrentPage,
|
|
989
1018
|
pageSize: docPageSize,
|
|
990
1019
|
includeChildOrganizations: userPreferences?.includeChildOrganizations ?? false,
|
|
991
|
-
sort: sortString
|
|
1020
|
+
sort: sortString,
|
|
1021
|
+
search: docSearchQuery.trim() || undefined
|
|
992
1022
|
});
|
|
993
1023
|
|
|
994
1024
|
if (result.success && result.data) {
|
|
@@ -1330,6 +1360,15 @@
|
|
|
1330
1360
|
</div>
|
|
1331
1361
|
</div>
|
|
1332
1362
|
<div class="flex items-center gap-1">
|
|
1363
|
+
<Button
|
|
1364
|
+
size="sm"
|
|
1365
|
+
variant="ghost"
|
|
1366
|
+
onclick={toggleDocSearch}
|
|
1367
|
+
class="h-8 w-8 p-0 {docSearchOpen || docSearchQuery ? 'bg-muted' : ''}"
|
|
1368
|
+
title="Search documents"
|
|
1369
|
+
>
|
|
1370
|
+
<Search class="h-4 w-4" />
|
|
1371
|
+
</Button>
|
|
1333
1372
|
{#if perms.can('document.create') && !schemas.find((s) => s.name === selectedDocumentType)?.singleton}
|
|
1334
1373
|
<Button
|
|
1335
1374
|
size="sm"
|
|
@@ -1459,6 +1498,37 @@
|
|
|
1459
1498
|
</Popover.Root>
|
|
1460
1499
|
</div>
|
|
1461
1500
|
</div>
|
|
1501
|
+
|
|
1502
|
+
{#if docSearchOpen}
|
|
1503
|
+
<div class="relative mt-2">
|
|
1504
|
+
<Search
|
|
1505
|
+
size={14}
|
|
1506
|
+
class="text-muted-foreground absolute top-1/2 left-2.5 -translate-y-1/2"
|
|
1507
|
+
/>
|
|
1508
|
+
<Input
|
|
1509
|
+
bind:ref={docSearchInputEl}
|
|
1510
|
+
placeholder="Search {pluralize(
|
|
1511
|
+
currentDocType?.title || selectedDocumentType
|
|
1512
|
+
).toLowerCase()}"
|
|
1513
|
+
class="h-8 pl-8 text-sm {docSearchQuery ? 'pr-8' : ''}"
|
|
1514
|
+
value={docSearchQuery}
|
|
1515
|
+
oninput={(e) =>
|
|
1516
|
+
handleDocSearchInput((e.target as HTMLInputElement).value)}
|
|
1517
|
+
onkeydown={(e) => {
|
|
1518
|
+
if (e.key === 'Escape') toggleDocSearch();
|
|
1519
|
+
}}
|
|
1520
|
+
/>
|
|
1521
|
+
{#if docSearchQuery}
|
|
1522
|
+
<button
|
|
1523
|
+
onclick={() => handleDocSearchInput('')}
|
|
1524
|
+
class="text-muted-foreground hover:text-foreground absolute top-1/2 right-2.5 -translate-y-1/2"
|
|
1525
|
+
title="Clear search"
|
|
1526
|
+
>
|
|
1527
|
+
<X class="h-3.5 w-3.5" />
|
|
1528
|
+
</button>
|
|
1529
|
+
{/if}
|
|
1530
|
+
</div>
|
|
1531
|
+
{/if}
|
|
1462
1532
|
</div>
|
|
1463
1533
|
|
|
1464
1534
|
<div class="flex-1 overflow-y-auto">
|
|
@@ -1547,10 +1617,17 @@
|
|
|
1547
1617
|
<FileText class="text-muted-foreground h-8 w-8" />
|
|
1548
1618
|
{/if}
|
|
1549
1619
|
</div>
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1620
|
+
{#if docSearchQuery}
|
|
1621
|
+
<h3 class="mb-2 font-medium">No matching documents</h3>
|
|
1622
|
+
<p class="text-muted-foreground text-sm">
|
|
1623
|
+
No results for "{docSearchQuery}". Try a different search.
|
|
1624
|
+
</p>
|
|
1625
|
+
{:else}
|
|
1626
|
+
<h3 class="mb-2 font-medium">No documents found</h3>
|
|
1627
|
+
<p class="text-muted-foreground text-sm">
|
|
1628
|
+
Create your first {selectedDocumentType} document using the + button above
|
|
1629
|
+
</p>
|
|
1630
|
+
{/if}
|
|
1554
1631
|
</div>
|
|
1555
1632
|
{/if}
|
|
1556
1633
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminApp.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminApp.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminApp.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminApp.svelte.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGxC,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAE1F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AA8BnE,UAAU,KAAK;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;IACjC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,eAAe,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAC;CAC7D;AAsqDF,QAAA,MAAM,QAAQ,0BAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|