@selvajs/platform 0.21.0-beta.3 → 1.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/access/index.d.ts +0 -1
- package/dist/access/index.js +0 -1
- package/dist/access/rules.d.ts +7 -14
- package/dist/access/rules.js +22 -26
- package/dist/apiTokens/index.d.ts +1 -2
- package/dist/apiTokens/index.js +1 -2
- package/dist/apiTokens/interface.d.ts +0 -1
- package/dist/apiTokens/interface.js +0 -1
- package/dist/apiTokens/scope.d.ts +0 -1
- package/dist/apiTokens/scope.js +0 -1
- package/dist/apiTokens/types.d.ts +0 -1
- package/dist/apiTokens/types.js +0 -1
- package/dist/auth/index.d.ts +0 -1
- package/dist/auth/index.js +0 -1
- package/dist/auth/interface.d.ts +4 -23
- package/dist/auth/interface.js +0 -1
- package/dist/auth/types.d.ts +4 -2
- package/dist/auth/types.js +0 -1
- package/dist/bindings/index.d.ts +0 -1
- package/dist/bindings/index.js +0 -1
- package/dist/bindings/interface.d.ts +0 -1
- package/dist/bindings/interface.js +0 -1
- package/dist/computeServer/index.d.ts +1 -7
- package/dist/computeServer/index.js +2 -8
- package/dist/computeServer/interface.d.ts +9 -6
- package/dist/computeServer/interface.js +0 -1
- package/dist/computeServer/public.d.ts +6 -0
- package/dist/computeServer/public.js +2 -0
- package/dist/computeServer/secrets.d.ts +12 -6
- package/dist/computeServer/secrets.js +35 -15
- package/dist/computeServer/types.d.ts +5 -2
- package/dist/computeServer/types.js +4 -1
- package/dist/computeServer/utils.d.ts +17 -6
- package/dist/computeServer/utils.js +31 -21
- package/dist/config.d.ts +1 -2
- package/dist/config.js +0 -1
- package/dist/context.d.ts +11 -1
- package/dist/context.js +51 -2
- package/dist/data/index.d.ts +0 -1
- package/dist/data/index.js +0 -1
- package/dist/data/interface.d.ts +0 -1
- package/dist/data/interface.js +0 -1
- package/dist/definitions/index.d.ts +2 -3
- package/dist/definitions/index.js +2 -3
- package/dist/definitions/interface.d.ts +0 -1
- package/dist/definitions/interface.js +0 -1
- package/dist/definitions/paths.d.ts +0 -1
- package/dist/definitions/paths.js +1 -12
- package/dist/definitions/schemas.d.ts +0 -21
- package/dist/definitions/schemas.js +6 -19
- package/dist/definitions/types.d.ts +3 -4
- package/dist/definitions/types.js +0 -3
- package/dist/errors/interface.d.ts +1 -2
- package/dist/errors/interface.js +0 -1
- package/dist/errors.d.ts +8 -1
- package/dist/errors.js +11 -1
- package/dist/events/audit.d.ts +1 -2
- package/dist/events/audit.js +0 -1
- package/dist/events/index.d.ts +0 -1
- package/dist/events/index.js +0 -1
- package/dist/events/interface.d.ts +0 -1
- package/dist/events/interface.js +0 -1
- package/dist/index.d.ts +18 -61
- package/dist/index.js +18 -24
- package/dist/invites/index.d.ts +0 -1
- package/dist/invites/index.js +0 -1
- package/dist/invites/interface.d.ts +5 -6
- package/dist/invites/interface.js +0 -1
- package/dist/invites/types.d.ts +0 -1
- package/dist/invites/types.js +0 -1
- package/dist/logging/interface.d.ts +0 -1
- package/dist/logging/interface.js +0 -1
- package/dist/metrics/interface.d.ts +0 -1
- package/dist/metrics/interface.js +0 -1
- package/dist/notifications/index.d.ts +0 -1
- package/dist/notifications/index.js +0 -1
- package/dist/notifications/interface.d.ts +1 -3
- package/dist/notifications/interface.js +1 -3
- package/dist/notifications/types.d.ts +0 -1
- package/dist/notifications/types.js +0 -1
- package/dist/organizations/index.d.ts +2 -3
- package/dist/organizations/index.js +1 -2
- package/dist/organizations/interface.d.ts +3 -5
- package/dist/organizations/interface.js +0 -1
- package/dist/organizations/paths.d.ts +1 -6
- package/dist/organizations/paths.js +2 -20
- package/dist/organizations/schemas.d.ts +5 -7
- package/dist/organizations/schemas.js +20 -13
- package/dist/organizations/types.d.ts +0 -1
- package/dist/organizations/types.js +0 -1
- package/dist/pagination.d.ts +21 -3
- package/dist/pagination.js +34 -1
- package/dist/permissions/index.d.ts +2 -2
- package/dist/permissions/index.js +2 -2
- package/dist/permissions/interface.d.ts +4 -4
- package/dist/permissions/interface.js +0 -1
- package/dist/permissions/types.d.ts +0 -1
- package/dist/permissions/types.js +0 -1
- package/dist/platformProjects/index.d.ts +1 -3
- package/dist/platformProjects/index.js +1 -2
- package/dist/platformProjects/interface.d.ts +0 -1
- package/dist/platformProjects/interface.js +0 -1
- package/dist/platformProjects/schemas.d.ts +0 -1
- package/dist/platformProjects/schemas.js +0 -1
- package/dist/platformProjects/types.d.ts +3 -2
- package/dist/platformProjects/types.js +0 -1
- package/dist/projects/index.d.ts +0 -1
- package/dist/projects/index.js +0 -1
- package/dist/projects/interface.d.ts +0 -1
- package/dist/projects/interface.js +0 -1
- package/dist/projects/schemas.d.ts +0 -1
- package/dist/projects/schemas.js +0 -1
- package/dist/projects/types.d.ts +0 -1
- package/dist/projects/types.js +0 -1
- package/dist/shareLinks/index.d.ts +0 -1
- package/dist/shareLinks/index.js +0 -1
- package/dist/shareLinks/interface.d.ts +6 -3
- package/dist/shareLinks/interface.js +0 -1
- package/dist/shareLinks/schemas.d.ts +0 -1
- package/dist/shareLinks/schemas.js +4 -2
- package/dist/shareLinks/types.d.ts +0 -1
- package/dist/shareLinks/types.js +0 -1
- package/dist/solveCache/interface.d.ts +2 -3
- package/dist/solveCache/interface.js +0 -1
- package/dist/storage/assetClasses.d.ts +4 -8
- package/dist/storage/assetClasses.js +6 -21
- package/dist/storage/cacheBust.d.ts +0 -1
- package/dist/storage/cacheBust.js +5 -3
- package/dist/storage/image.d.ts +8 -5
- package/dist/storage/image.js +74 -12
- package/dist/storage/index.d.ts +2 -4
- package/dist/storage/index.js +2 -3
- package/dist/storage/interface.d.ts +11 -3
- package/dist/storage/interface.js +0 -1
- package/dist/storage/safeKey.d.ts +7 -0
- package/dist/storage/safeKey.js +12 -0
- package/dist/testing/index.d.ts +5 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/suites/apiTokenStoreSuite.d.ts +0 -1
- package/dist/testing/suites/apiTokenStoreSuite.js +0 -1
- package/dist/testing/suites/auditQuerySuite.d.ts +18 -0
- package/dist/testing/suites/auditQuerySuite.js +73 -0
- package/dist/testing/suites/authProviderSuite.d.ts +14 -3
- package/dist/testing/suites/authProviderSuite.js +57 -32
- package/dist/testing/suites/computeServerStoreSuite.d.ts +1 -2
- package/dist/testing/suites/computeServerStoreSuite.js +68 -4
- package/dist/testing/suites/definitionStoreSuite.d.ts +2 -1
- package/dist/testing/suites/definitionStoreSuite.js +19 -16
- package/dist/testing/suites/emailLinkAuthSuite.d.ts +2 -5
- package/dist/testing/suites/emailLinkAuthSuite.js +1 -9
- package/dist/testing/suites/eventSinkSuite.d.ts +0 -1
- package/dist/testing/suites/eventSinkSuite.js +9 -1
- package/dist/testing/suites/helpers.d.ts +5 -1
- package/dist/testing/suites/helpers.js +7 -1
- package/dist/testing/suites/inviteStoreSuite.d.ts +0 -1
- package/dist/testing/suites/inviteStoreSuite.js +17 -7
- package/dist/testing/suites/oauthAuthSuite.d.ts +18 -0
- package/dist/testing/suites/oauthAuthSuite.js +26 -0
- package/dist/testing/suites/orgStoreSuite.d.ts +5 -1
- package/dist/testing/suites/orgStoreSuite.js +26 -7
- package/dist/testing/suites/platformPermissionStoreSuite.d.ts +0 -5
- package/dist/testing/suites/platformPermissionStoreSuite.js +12 -63
- package/dist/testing/suites/platformProjectGrantStoreSuite.d.ts +0 -1
- package/dist/testing/suites/platformProjectGrantStoreSuite.js +0 -5
- package/dist/testing/suites/projectStoreSuite.d.ts +5 -1
- package/dist/testing/suites/projectStoreSuite.js +6 -6
- package/dist/testing/suites/shareLinkStoreSuite.d.ts +2 -1
- package/dist/testing/suites/shareLinkStoreSuite.js +22 -25
- package/dist/testing/suites/solveMetricSinkSuite.d.ts +0 -1
- package/dist/testing/suites/solveMetricSinkSuite.js +2 -2
- package/dist/testing/suites/storageProviderSuite.d.ts +0 -1
- package/dist/testing/suites/storageProviderSuite.js +15 -10
- package/dist/testing/suites/userProfileStoreSuite.d.ts +1 -2
- package/dist/testing/suites/userProfileStoreSuite.js +42 -7
- package/dist/userProfile/index.d.ts +2 -2
- package/dist/userProfile/index.js +2 -2
- package/dist/userProfile/interface.d.ts +5 -6
- package/dist/userProfile/interface.js +0 -1
- package/dist/userProfile/types.d.ts +2 -1
- package/dist/userProfile/types.js +2 -1
- package/dist/utils/audit.d.ts +0 -1
- package/dist/utils/audit.js +0 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.js +0 -1
- package/package.json +7 -6
- package/dist/access/index.d.ts.map +0 -1
- package/dist/access/index.js.map +0 -1
- package/dist/access/rules.d.ts.map +0 -1
- package/dist/access/rules.js.map +0 -1
- package/dist/apiTokens/index.d.ts.map +0 -1
- package/dist/apiTokens/index.js.map +0 -1
- package/dist/apiTokens/interface.d.ts.map +0 -1
- package/dist/apiTokens/interface.js.map +0 -1
- package/dist/apiTokens/scope.d.ts.map +0 -1
- package/dist/apiTokens/scope.js.map +0 -1
- package/dist/apiTokens/types.d.ts.map +0 -1
- package/dist/apiTokens/types.js.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/interface.d.ts.map +0 -1
- package/dist/auth/interface.js.map +0 -1
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/auth/types.js.map +0 -1
- package/dist/bindings/index.d.ts.map +0 -1
- package/dist/bindings/index.js.map +0 -1
- package/dist/bindings/interface.d.ts.map +0 -1
- package/dist/bindings/interface.js.map +0 -1
- package/dist/computeServer/index.d.ts.map +0 -1
- package/dist/computeServer/index.js.map +0 -1
- package/dist/computeServer/interface.d.ts.map +0 -1
- package/dist/computeServer/interface.js.map +0 -1
- package/dist/computeServer/secrets.d.ts.map +0 -1
- package/dist/computeServer/secrets.js.map +0 -1
- package/dist/computeServer/types.d.ts.map +0 -1
- package/dist/computeServer/types.js.map +0 -1
- package/dist/computeServer/utils.d.ts.map +0 -1
- package/dist/computeServer/utils.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js.map +0 -1
- package/dist/data/index.d.ts.map +0 -1
- package/dist/data/index.js.map +0 -1
- package/dist/data/interface.d.ts.map +0 -1
- package/dist/data/interface.js.map +0 -1
- package/dist/definitions/index.d.ts.map +0 -1
- package/dist/definitions/index.js.map +0 -1
- package/dist/definitions/interface.d.ts.map +0 -1
- package/dist/definitions/interface.js.map +0 -1
- package/dist/definitions/paths.d.ts.map +0 -1
- package/dist/definitions/paths.js.map +0 -1
- package/dist/definitions/schemas.d.ts.map +0 -1
- package/dist/definitions/schemas.js.map +0 -1
- package/dist/definitions/types.d.ts.map +0 -1
- package/dist/definitions/types.js.map +0 -1
- package/dist/errors/interface.d.ts.map +0 -1
- package/dist/errors/interface.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/events/audit.d.ts.map +0 -1
- package/dist/events/audit.js.map +0 -1
- package/dist/events/index.d.ts.map +0 -1
- package/dist/events/index.js.map +0 -1
- package/dist/events/interface.d.ts.map +0 -1
- package/dist/events/interface.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invites/index.d.ts.map +0 -1
- package/dist/invites/index.js.map +0 -1
- package/dist/invites/interface.d.ts.map +0 -1
- package/dist/invites/interface.js.map +0 -1
- package/dist/invites/types.d.ts.map +0 -1
- package/dist/invites/types.js.map +0 -1
- package/dist/logging/interface.d.ts.map +0 -1
- package/dist/logging/interface.js.map +0 -1
- package/dist/metrics/interface.d.ts.map +0 -1
- package/dist/metrics/interface.js.map +0 -1
- package/dist/notifications/index.d.ts.map +0 -1
- package/dist/notifications/index.js.map +0 -1
- package/dist/notifications/interface.d.ts.map +0 -1
- package/dist/notifications/interface.js.map +0 -1
- package/dist/notifications/types.d.ts.map +0 -1
- package/dist/notifications/types.js.map +0 -1
- package/dist/organizations/index.d.ts.map +0 -1
- package/dist/organizations/index.js.map +0 -1
- package/dist/organizations/interface.d.ts.map +0 -1
- package/dist/organizations/interface.js.map +0 -1
- package/dist/organizations/paths.d.ts.map +0 -1
- package/dist/organizations/paths.js.map +0 -1
- package/dist/organizations/schemas.d.ts.map +0 -1
- package/dist/organizations/schemas.js.map +0 -1
- package/dist/organizations/types.d.ts.map +0 -1
- package/dist/organizations/types.js.map +0 -1
- package/dist/pagination.d.ts.map +0 -1
- package/dist/pagination.js.map +0 -1
- package/dist/permissions/index.d.ts.map +0 -1
- package/dist/permissions/index.js.map +0 -1
- package/dist/permissions/interface.d.ts.map +0 -1
- package/dist/permissions/interface.js.map +0 -1
- package/dist/permissions/types.d.ts.map +0 -1
- package/dist/permissions/types.js.map +0 -1
- package/dist/platformProjects/index.d.ts.map +0 -1
- package/dist/platformProjects/index.js.map +0 -1
- package/dist/platformProjects/interface.d.ts.map +0 -1
- package/dist/platformProjects/interface.js.map +0 -1
- package/dist/platformProjects/schemas.d.ts.map +0 -1
- package/dist/platformProjects/schemas.js.map +0 -1
- package/dist/platformProjects/types.d.ts.map +0 -1
- package/dist/platformProjects/types.js.map +0 -1
- package/dist/projects/index.d.ts.map +0 -1
- package/dist/projects/index.js.map +0 -1
- package/dist/projects/interface.d.ts.map +0 -1
- package/dist/projects/interface.js.map +0 -1
- package/dist/projects/schemas.d.ts.map +0 -1
- package/dist/projects/schemas.js.map +0 -1
- package/dist/projects/types.d.ts.map +0 -1
- package/dist/projects/types.js.map +0 -1
- package/dist/shareLinks/index.d.ts.map +0 -1
- package/dist/shareLinks/index.js.map +0 -1
- package/dist/shareLinks/interface.d.ts.map +0 -1
- package/dist/shareLinks/interface.js.map +0 -1
- package/dist/shareLinks/schemas.d.ts.map +0 -1
- package/dist/shareLinks/schemas.js.map +0 -1
- package/dist/shareLinks/types.d.ts.map +0 -1
- package/dist/shareLinks/types.js.map +0 -1
- package/dist/solveCache/interface.d.ts.map +0 -1
- package/dist/solveCache/interface.js.map +0 -1
- package/dist/storage/assetClasses.d.ts.map +0 -1
- package/dist/storage/assetClasses.js.map +0 -1
- package/dist/storage/cacheBust.d.ts.map +0 -1
- package/dist/storage/cacheBust.js.map +0 -1
- package/dist/storage/image.d.ts.map +0 -1
- package/dist/storage/image.js.map +0 -1
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/interface.d.ts.map +0 -1
- package/dist/storage/interface.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/suites/apiTokenStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/apiTokenStoreSuite.js.map +0 -1
- package/dist/testing/suites/authProviderSuite.d.ts.map +0 -1
- package/dist/testing/suites/authProviderSuite.js.map +0 -1
- package/dist/testing/suites/computeServerStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/computeServerStoreSuite.js.map +0 -1
- package/dist/testing/suites/definitionStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/definitionStoreSuite.js.map +0 -1
- package/dist/testing/suites/emailLinkAuthSuite.d.ts.map +0 -1
- package/dist/testing/suites/emailLinkAuthSuite.js.map +0 -1
- package/dist/testing/suites/eventSinkSuite.d.ts.map +0 -1
- package/dist/testing/suites/eventSinkSuite.js.map +0 -1
- package/dist/testing/suites/helpers.d.ts.map +0 -1
- package/dist/testing/suites/helpers.js.map +0 -1
- package/dist/testing/suites/inviteStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/inviteStoreSuite.js.map +0 -1
- package/dist/testing/suites/orgStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/orgStoreSuite.js.map +0 -1
- package/dist/testing/suites/platformPermissionStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/platformPermissionStoreSuite.js.map +0 -1
- package/dist/testing/suites/platformProjectGrantStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/platformProjectGrantStoreSuite.js.map +0 -1
- package/dist/testing/suites/projectStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/projectStoreSuite.js.map +0 -1
- package/dist/testing/suites/shareLinkStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/shareLinkStoreSuite.js.map +0 -1
- package/dist/testing/suites/solveMetricSinkSuite.d.ts.map +0 -1
- package/dist/testing/suites/solveMetricSinkSuite.js.map +0 -1
- package/dist/testing/suites/storageProviderSuite.d.ts.map +0 -1
- package/dist/testing/suites/storageProviderSuite.js.map +0 -1
- package/dist/testing/suites/userProfileStoreSuite.d.ts.map +0 -1
- package/dist/testing/suites/userProfileStoreSuite.js.map +0 -1
- package/dist/userProfile/index.d.ts.map +0 -1
- package/dist/userProfile/index.js.map +0 -1
- package/dist/userProfile/interface.d.ts.map +0 -1
- package/dist/userProfile/interface.js.map +0 -1
- package/dist/userProfile/types.d.ts.map +0 -1
- package/dist/userProfile/types.js.map +0 -1
- package/dist/utils/audit.d.ts.map +0 -1
- package/dist/utils/audit.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
// Conformance suite for IAuthProvider: token round-trips, identity, password auth, admin management.
|
|
2
2
|
import { describe, it, expect } from 'vitest';
|
|
3
3
|
import { makeUuid } from './helpers.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
it('verifyLogin returns a session token that verifyToken accepts', async () => {
|
|
9
|
-
const { provider, adminPassword, adminEmail = '' } = await createProvider();
|
|
10
|
-
const result = await provider.passwordAuth.verifyLogin(adminEmail, adminPassword);
|
|
11
|
-
expect(result.kind).toBe('success');
|
|
12
|
-
if (result.kind !== 'success')
|
|
13
|
-
return;
|
|
14
|
-
const verified = await provider.verifyToken(result.sessionToken);
|
|
15
|
-
expect(verified?.id).toBe(result.user.id);
|
|
16
|
-
});
|
|
4
|
+
/** Cases every auth provider must pass, password or not. */
|
|
5
|
+
export function runAuthCoreConformance(opts) {
|
|
6
|
+
const { name, createProvider } = opts;
|
|
7
|
+
describe(`IAuthProvider core conformance: ${name}`, () => {
|
|
17
8
|
it('verifyToken returns null for a garbage token', async () => {
|
|
18
9
|
const { provider } = await createProvider();
|
|
19
10
|
const result = await provider.verifyToken('not-a-valid-token');
|
|
@@ -24,35 +15,57 @@ export function runAuthProviderConformance(opts) {
|
|
|
24
15
|
const result = await provider.verifyToken('');
|
|
25
16
|
expect(result).toBeNull();
|
|
26
17
|
});
|
|
18
|
+
it('getUser returns null for unknown id', async () => {
|
|
19
|
+
const { provider } = await createProvider();
|
|
20
|
+
// Valid UUID shape but not an actual user — stricter backends (Postgres
|
|
21
|
+
// uuid columns) reject non-UUID ids outright, so we never send those.
|
|
22
|
+
const result = await provider.getUser(makeUuid());
|
|
23
|
+
expect(result).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Cases for a provider with `passwordAuth`. */
|
|
28
|
+
export function runPasswordAuthConformance(opts) {
|
|
29
|
+
const { name, createProvider, userManagement = false, selfRegistration = false } = opts;
|
|
30
|
+
describe(`IAuthProvider password conformance: ${name}`, () => {
|
|
31
|
+
// Token round-trips
|
|
32
|
+
it('verifyLogin returns a session token that verifyToken accepts', async () => {
|
|
33
|
+
const { provider, adminPassword, adminEmail = '' } = await createProvider();
|
|
34
|
+
const result = await provider.passwordAuth.verifyLogin(adminEmail, adminPassword);
|
|
35
|
+
expect(result.kind).toBe('success');
|
|
36
|
+
if (result.kind !== 'success')
|
|
37
|
+
return;
|
|
38
|
+
const verified = await provider.verifyToken(result.sessionToken);
|
|
39
|
+
expect(verified?.id).toBe(result.user.id);
|
|
40
|
+
});
|
|
27
41
|
// Password auth (only runs when passwordAuth is present)
|
|
28
42
|
it('verifyLogin returns success with correct password', async () => {
|
|
29
43
|
const { provider, adminPassword, adminEmail = '' } = await createProvider();
|
|
30
|
-
if (!provider.passwordAuth)
|
|
31
|
-
return;
|
|
32
44
|
const result = await provider.passwordAuth.verifyLogin(adminEmail, adminPassword);
|
|
33
45
|
expect(result.kind).toBe('success');
|
|
34
46
|
if (result.kind !== 'success')
|
|
35
47
|
return;
|
|
36
|
-
// Identity-only
|
|
48
|
+
// Identity-only: platform permissions live on IPlatformPermissionStore.
|
|
37
49
|
expect(result.user.id).toBeTruthy();
|
|
38
50
|
expect(typeof result.sessionToken).toBe('string');
|
|
39
51
|
expect(result.sessionToken.length).toBeGreaterThan(0);
|
|
40
52
|
});
|
|
53
|
+
if (selfRegistration) {
|
|
54
|
+
it('registerUser creates an account that can sign in', async () => {
|
|
55
|
+
const { provider } = await createProvider();
|
|
56
|
+
const email = `register-${makeUuid()}@example.com`;
|
|
57
|
+
const user = await provider.passwordAuth.registerUser(email, 'pass12345678');
|
|
58
|
+
expect(user?.email).toBe(email);
|
|
59
|
+
const login = await provider.passwordAuth.verifyLogin(email, 'pass12345678');
|
|
60
|
+
expect(login.kind).toBe('success');
|
|
61
|
+
});
|
|
62
|
+
}
|
|
41
63
|
it('verifyLogin returns failed for wrong password', async () => {
|
|
42
64
|
const { provider, adminEmail = '' } = await createProvider();
|
|
43
|
-
if (!provider.passwordAuth)
|
|
44
|
-
return;
|
|
45
65
|
const result = await provider.passwordAuth.verifyLogin(adminEmail, 'wrong-password-xyz');
|
|
46
66
|
expect(result.kind).toBe('failed');
|
|
47
67
|
});
|
|
48
68
|
// getUser
|
|
49
|
-
it('getUser returns null for unknown id', async () => {
|
|
50
|
-
const { provider } = await createProvider();
|
|
51
|
-
// Valid UUID shape but not an actual user — stricter backends (Postgres
|
|
52
|
-
// uuid columns) reject non-UUID ids outright, so we never send those.
|
|
53
|
-
const result = await provider.getUser(makeUuid());
|
|
54
|
-
expect(result).toBeNull();
|
|
55
|
-
});
|
|
56
69
|
// ============================================================================
|
|
57
70
|
// User management (opt-in)
|
|
58
71
|
// ============================================================================
|
|
@@ -102,12 +115,8 @@ export function runAuthProviderConformance(opts) {
|
|
|
102
115
|
const result = await provider.deleteUser(makeUuid());
|
|
103
116
|
expect(result).toBe('not_found');
|
|
104
117
|
});
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
// callers consult `countInstanceAdminsExcluding` before destructive
|
|
108
|
-
// ops. Auth-side delete/disable just deletes the identity and
|
|
109
|
-
// trusts the caller. See `runPlatformPermissionStoreConformance`
|
|
110
|
-
// for the invariant tests.
|
|
118
|
+
// Auth-side delete/disable trusts the caller: the sole-`instance_admin`
|
|
119
|
+
// invariant is `runPlatformPermissionStoreConformance`'s to test.
|
|
111
120
|
it('createUserWithPassword returns an identity record without profile fields', async () => {
|
|
112
121
|
const { provider } = await createProvider();
|
|
113
122
|
const user = await provider.passwordAuth.createUserWithPassword(`new-${makeUuid()}@example.com`, 'pass12345678');
|
|
@@ -127,6 +136,14 @@ export function runAuthProviderConformance(opts) {
|
|
|
127
136
|
const fetched = await provider.getUser(created.id);
|
|
128
137
|
expect(fetched?.disabled).toBe(true);
|
|
129
138
|
});
|
|
139
|
+
it('verifyLogin refuses a disabled user', async () => {
|
|
140
|
+
const { provider } = await createProvider();
|
|
141
|
+
const email = `disabled-${makeUuid()}@example.com`;
|
|
142
|
+
const created = await provider.passwordAuth.createUserWithPassword(email, 'pass12345678');
|
|
143
|
+
await provider.disableUser(created.id);
|
|
144
|
+
const result = await provider.passwordAuth.verifyLogin(email, 'pass12345678');
|
|
145
|
+
expect(result).toMatchObject({ kind: 'failed' });
|
|
146
|
+
});
|
|
130
147
|
it('disableUser returns not_found for unknown user', async () => {
|
|
131
148
|
const { provider } = await createProvider();
|
|
132
149
|
const result = await provider.disableUser(makeUuid());
|
|
@@ -156,4 +173,12 @@ export function runAuthProviderConformance(opts) {
|
|
|
156
173
|
}
|
|
157
174
|
});
|
|
158
175
|
}
|
|
159
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Both of the above, gated on what the provider has. New cases go behind an option
|
|
178
|
+
* so a third-party adapter does not turn red on upgrade.
|
|
179
|
+
*/
|
|
180
|
+
export function runAuthProviderConformance(opts) {
|
|
181
|
+
runAuthCoreConformance(opts);
|
|
182
|
+
if (!opts.passwordless)
|
|
183
|
+
runPasswordAuthConformance(opts);
|
|
184
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapter conformance suite for IComputeServerStore.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Servers are scoped (platform vs. org-private), with a global
|
|
5
5
|
* `defaultServerId` and per-org `orgDefaults`. The store surface exposes
|
|
6
6
|
* scope-targeted mutations; visibility filtering is done by callers via
|
|
7
7
|
* the pure helpers in `@selvajs/platform`.
|
|
@@ -18,4 +18,3 @@ export interface ComputeServerStoreConformanceOptions {
|
|
|
18
18
|
seedOrg?: (orgId: string) => Promise<void>;
|
|
19
19
|
}
|
|
20
20
|
export declare function runComputeServerStoreConformance(opts: ComputeServerStoreConformanceOptions): void;
|
|
21
|
-
//# sourceMappingURL=computeServerStoreSuite.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapter conformance suite for IComputeServerStore.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Servers are scoped (platform vs. org-private), with a global
|
|
5
5
|
* `defaultServerId` and per-org `orgDefaults`. The store surface exposes
|
|
6
6
|
* scope-targeted mutations; visibility filtering is done by callers via
|
|
7
7
|
* the pure helpers in `@selvajs/platform`.
|
|
@@ -65,11 +65,76 @@ export function runComputeServerStoreConformance(opts) {
|
|
|
65
65
|
it('getConfig on empty returns an empty servers list', async () => {
|
|
66
66
|
const store = await createStore();
|
|
67
67
|
const got = await store.getConfig(SYSTEM_CONTEXT);
|
|
68
|
-
expect(
|
|
68
|
+
expect(got.servers).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
it('getConfig hides keys unless asked, and reports that one is stored', async () => {
|
|
71
|
+
const store = await createStore();
|
|
72
|
+
const id = makeUuid();
|
|
73
|
+
await store.savePlatformServers(SYSTEM_CONTEXT, [
|
|
74
|
+
{
|
|
75
|
+
id,
|
|
76
|
+
scope: 'platform',
|
|
77
|
+
sharedWith: 'all',
|
|
78
|
+
label: 'Keyed',
|
|
79
|
+
serverUrl: 'http://compute.test',
|
|
80
|
+
apiKey: 'rhino-key'
|
|
81
|
+
}
|
|
82
|
+
], id);
|
|
83
|
+
const plain = await store.getConfig(SYSTEM_CONTEXT);
|
|
84
|
+
expect(plain.servers[0].apiKey).toBeUndefined();
|
|
85
|
+
expect(plain.servers[0].hasApiKey).toBe(true);
|
|
86
|
+
const keyed = await store.getConfig(SYSTEM_CONTEXT, { includeApiKeys: true });
|
|
87
|
+
expect(keyed.servers[0].apiKey).toBe('rhino-key');
|
|
88
|
+
});
|
|
89
|
+
it('getServerApiKey returns one key, and undefined for an unknown server', async () => {
|
|
90
|
+
const store = await createStore();
|
|
91
|
+
const id = makeUuid();
|
|
92
|
+
await store.savePlatformServers(SYSTEM_CONTEXT, [
|
|
93
|
+
{
|
|
94
|
+
id,
|
|
95
|
+
scope: 'platform',
|
|
96
|
+
sharedWith: 'all',
|
|
97
|
+
label: 'Keyed',
|
|
98
|
+
serverUrl: 'http://compute.test',
|
|
99
|
+
apiKey: 'rhino-key'
|
|
100
|
+
}
|
|
101
|
+
], id);
|
|
102
|
+
expect(await store.getServerApiKey(SYSTEM_CONTEXT, id)).toBe('rhino-key');
|
|
103
|
+
expect(await store.getServerApiKey(SYSTEM_CONTEXT, makeUuid())).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
it('verifySecrets passes on keys this store wrote', async () => {
|
|
106
|
+
const store = await createStore();
|
|
107
|
+
const id = makeUuid();
|
|
108
|
+
await store.savePlatformServers(SYSTEM_CONTEXT, [
|
|
109
|
+
{
|
|
110
|
+
id,
|
|
111
|
+
scope: 'platform',
|
|
112
|
+
sharedWith: 'all',
|
|
113
|
+
label: 'Keyed',
|
|
114
|
+
serverUrl: 'http://compute.test',
|
|
115
|
+
apiKey: 'rhino-key'
|
|
116
|
+
}
|
|
117
|
+
], id);
|
|
118
|
+
const report = await store.verifySecrets?.();
|
|
119
|
+
if (report)
|
|
120
|
+
expect(report).toEqual({ ok: true, failures: [], plaintextFound: false });
|
|
69
121
|
});
|
|
70
122
|
// ============================================================================
|
|
71
|
-
// Org-private servers + orgDefaults
|
|
123
|
+
// Org-private servers + orgDefaults
|
|
72
124
|
// ============================================================================
|
|
125
|
+
it('saveOrgServers ignores an entry that is not an org server', async () => {
|
|
126
|
+
const store = await createStore();
|
|
127
|
+
const orgId = await seed();
|
|
128
|
+
const stray = {
|
|
129
|
+
id: makeUuid(),
|
|
130
|
+
scope: 'platform',
|
|
131
|
+
sharedWith: 'all',
|
|
132
|
+
label: 'Stray',
|
|
133
|
+
serverUrl: 'http://compute.test'
|
|
134
|
+
};
|
|
135
|
+
await store.saveOrgServers(SYSTEM_CONTEXT, orgId, [stray]);
|
|
136
|
+
expect((await store.getConfig(SYSTEM_CONTEXT)).servers).toEqual([]);
|
|
137
|
+
});
|
|
73
138
|
it('saveOrgServers stores rows owned by the org', async () => {
|
|
74
139
|
const store = await createStore();
|
|
75
140
|
const orgId = await seed();
|
|
@@ -136,4 +201,3 @@ export function runComputeServerStoreConformance(opts) {
|
|
|
136
201
|
});
|
|
137
202
|
});
|
|
138
203
|
}
|
|
139
|
-
//# sourceMappingURL=computeServerStoreSuite.js.map
|
|
@@ -26,6 +26,8 @@ export interface DefinitionTestScope {
|
|
|
26
26
|
* Must reference a real row for FK-enforcing adapters.
|
|
27
27
|
*/
|
|
28
28
|
secondaryProjectId: string;
|
|
29
|
+
/** Used by the ctx-isolation cases when `projectId` is one anyone can see. */
|
|
30
|
+
privateProjectId?: string;
|
|
29
31
|
/** Optional third user id for ctx-isolation tests. Required when ctxIsolation is set. */
|
|
30
32
|
secondaryUserId?: string;
|
|
31
33
|
/** Token for `secondaryUserId`, when used. */
|
|
@@ -49,4 +51,3 @@ export interface DefinitionStoreConformanceOptions {
|
|
|
49
51
|
ctxIsolation?: boolean;
|
|
50
52
|
}
|
|
51
53
|
export declare function runDefinitionStoreConformance(opts: DefinitionStoreConformanceOptions): void;
|
|
52
|
-
//# sourceMappingURL=definitionStoreSuite.d.ts.map
|
|
@@ -6,25 +6,21 @@
|
|
|
6
6
|
* reference protection (Permissions spec §6), and error shapes.
|
|
7
7
|
*/
|
|
8
8
|
import { describe, it, expect } from 'vitest';
|
|
9
|
-
import {
|
|
9
|
+
import { makeSeedHelpers, makeUuid } from './helpers.js';
|
|
10
10
|
const DEFAULT_SCOPE = {
|
|
11
11
|
ownerId: 'user-1',
|
|
12
12
|
projectId: 'project-1',
|
|
13
13
|
secondaryProjectId: 'project-2',
|
|
14
14
|
secondaryUserId: 'user-b'
|
|
15
15
|
};
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
const tokens = new Map();
|
|
20
|
-
function ctx(userId, opts = {}) {
|
|
21
|
-
return makeCtx(userId, { ...opts, sessionToken: tokens.get(userId) });
|
|
22
|
-
}
|
|
16
|
+
// `scopeFor` registers the scope's tokens, so `ctx` runs as the right user on an
|
|
17
|
+
// adapter with DB-side auth (Supabase RLS). This suite seeds nobody itself.
|
|
18
|
+
const { ctx, registerToken } = makeSeedHelpers(async (userId) => ({ userId }));
|
|
23
19
|
function recordTokensFromScope(scope) {
|
|
24
20
|
if (scope.ownerSessionToken)
|
|
25
|
-
|
|
21
|
+
registerToken(scope.ownerId, scope.ownerSessionToken);
|
|
26
22
|
if (scope.secondaryUserId && scope.secondaryUserSessionToken) {
|
|
27
|
-
|
|
23
|
+
registerToken(scope.secondaryUserId, scope.secondaryUserSessionToken);
|
|
28
24
|
}
|
|
29
25
|
}
|
|
30
26
|
function record(scope, overrides = {}) {
|
|
@@ -163,6 +159,15 @@ export function runDefinitionStoreConformance(opts) {
|
|
|
163
159
|
expect(got?.displayName).toBe('New');
|
|
164
160
|
expect(got.updatedAt >= created.updatedAt).toBe(true);
|
|
165
161
|
});
|
|
162
|
+
it('update with null tags clears them', async () => {
|
|
163
|
+
const store = await createStore();
|
|
164
|
+
const scope = await scopeFor();
|
|
165
|
+
const guid = makeUuid();
|
|
166
|
+
await store.create(ctx(scope.ownerId), record(scope, { guid, tags: ['roof'] }));
|
|
167
|
+
await store.update(ctx(scope.ownerId), guid, { tags: null });
|
|
168
|
+
const got = await store.get(ctx(scope.ownerId), guid);
|
|
169
|
+
expect(got?.tags ?? []).toEqual([]);
|
|
170
|
+
});
|
|
166
171
|
it('solveCacheLimit round-trips: absent → inherit, set → value, null → cleared', async () => {
|
|
167
172
|
const store = await createStore();
|
|
168
173
|
const scope = await scopeFor();
|
|
@@ -269,9 +274,8 @@ export function runDefinitionStoreConformance(opts) {
|
|
|
269
274
|
expect(got?.nextVersionNumber).toBe(4);
|
|
270
275
|
});
|
|
271
276
|
it('reserveNextVersionNumber mints a FRESH number after delete-latest (fileKeys never collide)', async () => {
|
|
272
|
-
// The
|
|
273
|
-
//
|
|
274
|
-
// counter must hand out a number BEYOND any deleted version's.
|
|
277
|
+
// The counter exists because max(existing)+1 would reuse a deleted number
|
|
278
|
+
// and collide the fileKey, so it must go beyond any deleted version's.
|
|
275
279
|
const store = await createStore();
|
|
276
280
|
const scope = await scopeFor();
|
|
277
281
|
const guid = makeUuid();
|
|
@@ -574,7 +578,7 @@ export function runDefinitionStoreConformance(opts) {
|
|
|
574
578
|
if (!otherUser)
|
|
575
579
|
throw new Error('ctxIsolation requires scope.secondaryUserId');
|
|
576
580
|
const guid = makeUuid();
|
|
577
|
-
await store.create(ctx(scope.ownerId), record(scope, { guid }));
|
|
581
|
+
await store.create(ctx(scope.ownerId), record(scope, { guid, projectId: scope.privateProjectId ?? scope.projectId }));
|
|
578
582
|
const page = await store.list(ctx(otherUser));
|
|
579
583
|
expect(page.items.map((r) => r.guid)).not.toContain(guid);
|
|
580
584
|
});
|
|
@@ -585,11 +589,10 @@ export function runDefinitionStoreConformance(opts) {
|
|
|
585
589
|
if (!otherUser)
|
|
586
590
|
throw new Error('ctxIsolation requires scope.secondaryUserId');
|
|
587
591
|
const guid = makeUuid();
|
|
588
|
-
await store.create(ctx(scope.ownerId), record(scope, { guid }));
|
|
592
|
+
await store.create(ctx(scope.ownerId), record(scope, { guid, projectId: scope.privateProjectId ?? scope.projectId }));
|
|
589
593
|
const got = await store.get(ctx(otherUser), guid);
|
|
590
594
|
expect(got).toBeNull();
|
|
591
595
|
});
|
|
592
596
|
}
|
|
593
597
|
});
|
|
594
598
|
}
|
|
595
|
-
//# sourceMappingURL=definitionStoreSuite.js.map
|
|
@@ -18,15 +18,12 @@ export interface EmailLinkAuthConformanceOptions {
|
|
|
18
18
|
* with self-signup enabled or disabled — the suite's tests are robust to
|
|
19
19
|
* either, since they don't assume new users get created.
|
|
20
20
|
*
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
23
|
-
* one the adapter must reject as malformed.
|
|
21
|
+
* `invalidEmail` is an address the adapter must reject as malformed. A valid
|
|
22
|
+
* send isn't tested here: it needs real mail delivery.
|
|
24
23
|
*/
|
|
25
24
|
createAdapter: () => Promise<{
|
|
26
25
|
adapter: IEmailLinkAuth;
|
|
27
|
-
validEmail: string;
|
|
28
26
|
invalidEmail: string;
|
|
29
27
|
}>;
|
|
30
28
|
}
|
|
31
29
|
export declare function runEmailLinkAuthConformance(opts: EmailLinkAuthConformanceOptions): void;
|
|
32
|
-
//# sourceMappingURL=emailLinkAuthSuite.d.ts.map
|
|
@@ -36,16 +36,8 @@ export function runEmailLinkAuthConformance(opts) {
|
|
|
36
36
|
const result = await adapter.sendMagicLink(invalidEmail, 'https://example.com/auth/email/callback');
|
|
37
37
|
expect(result.ok).toBe(false);
|
|
38
38
|
if (!result.ok) {
|
|
39
|
-
expect(
|
|
39
|
+
expect(result.reason).toBe('invalid_email');
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
|
-
// `sendMagicLink` for a valid email is intentionally NOT tested here:
|
|
43
|
-
// it requires real Supabase project credentials with email delivery
|
|
44
|
-
// enabled, and we'd be asserting on side-effects (mail sent) we can't
|
|
45
|
-
// observe. Adapter-specific integration tests cover that path.
|
|
46
|
-
void '';
|
|
47
42
|
});
|
|
48
|
-
// keep `createAdapter` referenced for adapters that have nothing more to test
|
|
49
|
-
void createAdapter;
|
|
50
43
|
}
|
|
51
|
-
//# sourceMappingURL=emailLinkAuthSuite.js.map
|
|
@@ -151,6 +151,15 @@ export function runEventSinkConformance(opts) {
|
|
|
151
151
|
await expect(provider.orgs.createOrg(ctx, makeOrg({ id: orgId, ownerId: actorId }))).rejects.toThrow();
|
|
152
152
|
expect(sink.ofType('org.created')).toHaveLength(0);
|
|
153
153
|
});
|
|
154
|
+
it('stamps tokenId on an event from an API-token request', async () => {
|
|
155
|
+
const tokenId = makeUuid();
|
|
156
|
+
const keyed = {
|
|
157
|
+
...ctx,
|
|
158
|
+
apiScope: { tokenId, scopes: [], expiresAt: '2099-01-01T00:00:00.000Z' }
|
|
159
|
+
};
|
|
160
|
+
await provider.orgs.createOrg(keyed, makeOrg({ id: makeUuid(), ownerId: actorId }));
|
|
161
|
+
expect(sink.ofType('org.created')[0]).toMatchObject({ actorId, tokenId });
|
|
162
|
+
});
|
|
154
163
|
it('system-context mutations emit with actorId="system"', async () => {
|
|
155
164
|
const sysCtx = {
|
|
156
165
|
userId: '',
|
|
@@ -261,4 +270,3 @@ function makeShareLink(overrides) {
|
|
|
261
270
|
solveCount: 0
|
|
262
271
|
};
|
|
263
272
|
}
|
|
264
|
-
//# sourceMappingURL=eventSinkSuite.js.map
|
|
@@ -14,6 +14,11 @@ export declare function makeCtx(userId: string, opts?: {
|
|
|
14
14
|
orgPermissions?: RequestContext['orgPermissions'];
|
|
15
15
|
sessionToken?: string;
|
|
16
16
|
}): RequestContext;
|
|
17
|
+
/**
|
|
18
|
+
* A system context holding no permissions of its own, for proving a store leans on
|
|
19
|
+
* `system` rather than on a permission `SYSTEM_CONTEXT` happens to carry.
|
|
20
|
+
*/
|
|
21
|
+
export declare function makeSystemCtx(userId?: string): RequestContext;
|
|
17
22
|
export declare function makeUuid(): string;
|
|
18
23
|
/**
|
|
19
24
|
* Optional hook adapters pass when their backend enforces referential
|
|
@@ -52,4 +57,3 @@ export declare function makeSeedHelpers(seedUser: SeedUserFn): {
|
|
|
52
57
|
registerToken: (userId: string, sessionToken: string) => void;
|
|
53
58
|
tokens: Map<string, string>;
|
|
54
59
|
};
|
|
55
|
-
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -17,6 +17,13 @@ export function makeCtx(userId, opts = {}) {
|
|
|
17
17
|
adapterContext: opts.sessionToken ? { sessionToken: opts.sessionToken } : undefined
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* A system context holding no permissions of its own, for proving a store leans on
|
|
22
|
+
* `system` rather than on a permission `SYSTEM_CONTEXT` happens to carry.
|
|
23
|
+
*/
|
|
24
|
+
export function makeSystemCtx(userId = '') {
|
|
25
|
+
return { userId, platformPermissions: [], orgPermissions: [], system: true };
|
|
26
|
+
}
|
|
20
27
|
export function makeUuid() {
|
|
21
28
|
return crypto.randomUUID();
|
|
22
29
|
}
|
|
@@ -44,4 +51,3 @@ export function makeSeedHelpers(seedUser) {
|
|
|
44
51
|
}
|
|
45
52
|
return { seed, ctx, registerToken, tokens };
|
|
46
53
|
}
|
|
47
|
-
//# sourceMappingURL=helpers.js.map
|
|
@@ -81,23 +81,34 @@ export function runInviteStoreConformance(opts) {
|
|
|
81
81
|
await store.markAccepted(SYSTEM_CONTEXT, inv.id, acceptor);
|
|
82
82
|
const got = await store.getByTokenHash(SYSTEM_CONTEXT, inv.tokenHash);
|
|
83
83
|
expect(got).toBeNull();
|
|
84
|
+
const page = await store.listByOrg(ctx(scope.adminId), scope.orgId, { limit: 100 });
|
|
85
|
+
const row = page.items.find((i) => i.id === inv.id);
|
|
86
|
+
expect(row?.acceptedAt).toBeTruthy();
|
|
87
|
+
expect(row?.acceptedByUserId).toBe(acceptor);
|
|
84
88
|
});
|
|
85
|
-
it('markAccepted is idempotent
|
|
89
|
+
it('markAccepted is idempotent: the first acceptor stays', async () => {
|
|
86
90
|
const store = await createStore();
|
|
87
91
|
const scope = await scopeFor();
|
|
88
92
|
const { userId: acceptor } = await seedUser(makeUuid());
|
|
89
93
|
const inv = invite(scope);
|
|
90
94
|
await store.create(ctx(scope.adminId), inv);
|
|
91
|
-
await
|
|
92
|
-
await store.markAccepted(SYSTEM_CONTEXT, inv.id, acceptor);
|
|
95
|
+
const { userId: second } = await seedUser(makeUuid());
|
|
96
|
+
expect(await store.markAccepted(SYSTEM_CONTEXT, inv.id, acceptor)).toBe(true);
|
|
97
|
+
expect(await store.markAccepted(SYSTEM_CONTEXT, inv.id, second)).toBe(false);
|
|
98
|
+
const page = await store.listByOrg(ctx(scope.adminId), scope.orgId, { limit: 100 });
|
|
99
|
+
expect(page.items.find((i) => i.id === inv.id)?.acceptedByUserId).toBe(acceptor);
|
|
93
100
|
});
|
|
94
101
|
it('listByOrg returns invites for the org', async () => {
|
|
95
102
|
const store = await createStore();
|
|
96
103
|
const scope = await scopeFor();
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
const ids = [];
|
|
105
|
+
for (let i = 0; i < 3; i++) {
|
|
106
|
+
const inv = invite(scope);
|
|
107
|
+
ids.push(inv.id);
|
|
108
|
+
await store.create(ctx(scope.adminId), inv);
|
|
109
|
+
}
|
|
99
110
|
const page = await store.listByOrg(ctx(scope.adminId), scope.orgId, { limit: 100 });
|
|
100
|
-
expect(page.items.
|
|
111
|
+
expect(page.items.map((i) => i.id)).toEqual(expect.arrayContaining(ids));
|
|
101
112
|
});
|
|
102
113
|
it('revoke removes a pending invite', async () => {
|
|
103
114
|
const store = await createStore();
|
|
@@ -157,4 +168,3 @@ export function runInviteStoreConformance(opts) {
|
|
|
157
168
|
});
|
|
158
169
|
});
|
|
159
170
|
}
|
|
160
|
-
//# sourceMappingURL=inviteStoreSuite.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter conformance suite for IOAuthAuth and ISessionRefresh.
|
|
3
|
+
*
|
|
4
|
+
* Covers what can be checked in process: provider listing and the refusal of bad
|
|
5
|
+
* credentials. A real code exchange needs a browser at the IdP.
|
|
6
|
+
*/
|
|
7
|
+
import type { IOAuthAuth, ISessionRefresh } from '../../auth/index.js';
|
|
8
|
+
export interface OAuthAuthConformanceOptions {
|
|
9
|
+
name: string;
|
|
10
|
+
createAdapter: () => Promise<{
|
|
11
|
+
oauth: IOAuthAuth;
|
|
12
|
+
sessionRefresh: ISessionRefresh;
|
|
13
|
+
}> | {
|
|
14
|
+
oauth: IOAuthAuth;
|
|
15
|
+
sessionRefresh: ISessionRefresh;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare function runOAuthAuthConformance(opts: OAuthAuthConformanceOptions): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter conformance suite for IOAuthAuth and ISessionRefresh.
|
|
3
|
+
*
|
|
4
|
+
* Covers what can be checked in process: provider listing and the refusal of bad
|
|
5
|
+
* credentials. A real code exchange needs a browser at the IdP.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
export function runOAuthAuthConformance(opts) {
|
|
9
|
+
const { name, createAdapter } = opts;
|
|
10
|
+
describe(`IOAuthAuth conformance: ${name}`, () => {
|
|
11
|
+
it('listProviders names providers in lowercase', async () => {
|
|
12
|
+
const { oauth } = await createAdapter();
|
|
13
|
+
for (const provider of oauth.listProviders()) {
|
|
14
|
+
expect(provider).toBe(provider.toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
it('exchangeOAuthCode returns null for a code nobody issued', async () => {
|
|
18
|
+
const { oauth } = await createAdapter();
|
|
19
|
+
expect(await oauth.exchangeOAuthCode('not-a-real-code')).toBeNull();
|
|
20
|
+
});
|
|
21
|
+
it('refreshSession returns null for a token nobody issued', async () => {
|
|
22
|
+
const { sessionRefresh } = await createAdapter();
|
|
23
|
+
expect(await sessionRefresh.refreshSession('not-a-real-token')).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -19,6 +19,11 @@ export interface OrgStoreConformanceOptions {
|
|
|
19
19
|
* actually stored — callers use that, not the suggested one.
|
|
20
20
|
*/
|
|
21
21
|
seedUser?: SeedUserFn;
|
|
22
|
+
/**
|
|
23
|
+
* Seeds the other party in the ctx-isolation cases. Needed where `seedUser` makes
|
|
24
|
+
* instance admins, who see everything by design.
|
|
25
|
+
*/
|
|
26
|
+
seedOutsider?: SeedUserFn;
|
|
22
27
|
/** If true, skip createOrg/deleteOrg tests (e.g. single-org providers). */
|
|
23
28
|
singleOrgMode?: boolean;
|
|
24
29
|
/** If true, run ctx-isolation tests (adapters with row-level security). */
|
|
@@ -39,4 +44,3 @@ export interface OrgStoreConformanceOptions {
|
|
|
39
44
|
};
|
|
40
45
|
}
|
|
41
46
|
export declare function runOrgStoreConformance(opts: OrgStoreConformanceOptions): void;
|
|
42
|
-
//# sourceMappingURL=orgStoreSuite.d.ts.map
|
|
@@ -37,8 +37,9 @@ function member(orgId, userId, role = 'member') {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
export function runOrgStoreConformance(opts) {
|
|
40
|
-
const { name, createStore, seedUser = noopSeedUser, singleOrgMode = false, ctxIsolation = false, createCompanionStores } = opts;
|
|
40
|
+
const { name, createStore, seedUser = noopSeedUser, seedOutsider = seedUser, singleOrgMode = false, ctxIsolation = false, createCompanionStores } = opts;
|
|
41
41
|
const { seed, ctx } = makeSeedHelpers(seedUser);
|
|
42
|
+
const outsider = makeSeedHelpers(seedOutsider);
|
|
42
43
|
describe(`IOrgStore conformance: ${name}`, () => {
|
|
43
44
|
// ============================================================================
|
|
44
45
|
// Organizations
|
|
@@ -116,7 +117,7 @@ export function runOrgStoreConformance(opts) {
|
|
|
116
117
|
});
|
|
117
118
|
}
|
|
118
119
|
else {
|
|
119
|
-
it('
|
|
120
|
+
it('listOrgs returns the default organization', async () => {
|
|
120
121
|
const store = await createStore();
|
|
121
122
|
const u1 = await seed();
|
|
122
123
|
const orgs = await store.listOrgs(ctx(u1));
|
|
@@ -190,6 +191,25 @@ export function runOrgStoreConformance(opts) {
|
|
|
190
191
|
const got = await store.getOrgMember(ctx(u1), orgId, u2);
|
|
191
192
|
expect(got?.role).toBe('admin');
|
|
192
193
|
});
|
|
194
|
+
it('updateOrgMemberPermissions replaces the set and leaves the role', async () => {
|
|
195
|
+
const store = await createStore();
|
|
196
|
+
const { u1, orgId } = await setupOrg(store);
|
|
197
|
+
const u2 = await seed();
|
|
198
|
+
await store.addOrgMember(ctx(u1), member(orgId, u2));
|
|
199
|
+
const before = await store.getOrgMember(ctx(u1), orgId, u2);
|
|
200
|
+
await store.updateOrgMemberPermissions(ctx(u1), orgId, u2, ['manage_projects']);
|
|
201
|
+
const after = await store.getOrgMember(ctx(u1), orgId, u2);
|
|
202
|
+
expect(after?.permissions).toEqual(['manage_projects']);
|
|
203
|
+
expect(after?.role).toBe(before?.role);
|
|
204
|
+
});
|
|
205
|
+
it('removeOrgMember throws 404 for someone who is not a member', async () => {
|
|
206
|
+
const store = await createStore();
|
|
207
|
+
const { u1, orgId } = await setupOrg(store);
|
|
208
|
+
const stranger = await seed();
|
|
209
|
+
await expect(store.removeOrgMember(ctx(u1), orgId, stranger)).rejects.toMatchObject({
|
|
210
|
+
statusCode: 404
|
|
211
|
+
});
|
|
212
|
+
});
|
|
193
213
|
it('removeOrgMember deletes the member', async () => {
|
|
194
214
|
const store = await createStore();
|
|
195
215
|
const { u1, orgId } = await setupOrg(store);
|
|
@@ -347,22 +367,21 @@ export function runOrgStoreConformance(opts) {
|
|
|
347
367
|
it('ctx isolation: orgs created by one user are not visible to another', async () => {
|
|
348
368
|
const store = await createStore();
|
|
349
369
|
const userA = await seed();
|
|
350
|
-
const userB = await seed();
|
|
370
|
+
const userB = await outsider.seed();
|
|
351
371
|
const orgA = makeUuid();
|
|
352
372
|
await store.createOrg(ctx(userA), org(userA, { id: orgA, slug: `org-a-${orgA.slice(0, 8)}` }));
|
|
353
|
-
const page = await store.listOrgs(ctx(userB));
|
|
373
|
+
const page = await store.listOrgs(outsider.ctx(userB));
|
|
354
374
|
expect(page.items.map((o) => o.id)).not.toContain(orgA);
|
|
355
375
|
});
|
|
356
376
|
it('ctx isolation: user cannot get an org they do not own', async () => {
|
|
357
377
|
const store = await createStore();
|
|
358
378
|
const userA = await seed();
|
|
359
|
-
const userB = await seed();
|
|
379
|
+
const userB = await outsider.seed();
|
|
360
380
|
const orgA = makeUuid();
|
|
361
381
|
await store.createOrg(ctx(userA), org(userA, { id: orgA, slug: `org-a-${orgA.slice(0, 8)}` }));
|
|
362
|
-
const got = await store.getOrg(ctx(userB), orgA);
|
|
382
|
+
const got = await store.getOrg(outsider.ctx(userB), orgA);
|
|
363
383
|
expect(got).toBeNull();
|
|
364
384
|
});
|
|
365
385
|
}
|
|
366
386
|
});
|
|
367
387
|
}
|
|
368
|
-
//# sourceMappingURL=orgStoreSuite.js.map
|
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
* `countInstanceAdminsExcluding`, plus the §2 sole-`instance_admin`
|
|
6
6
|
* invariant — `set` must return `'last_admin'` when dropping the only admin,
|
|
7
7
|
* and `countInstanceAdminsExcluding` must exclude the named user.
|
|
8
|
-
*
|
|
9
|
-
* These tests used to live on the auth-provider conformance suite when the
|
|
10
|
-
* invariant was enforced there; the move from `IAuthProvider` to
|
|
11
|
-
* `IPlatformPermissionStore` brought them with it.
|
|
12
8
|
*/
|
|
13
9
|
import type { IPlatformPermissionStore } from '../../index.js';
|
|
14
10
|
export interface PlatformPermissionStoreConformanceOptions {
|
|
@@ -34,4 +30,3 @@ export interface PlatformPermissionStoreConformanceOptions {
|
|
|
34
30
|
cleanup?: () => Promise<void> | void;
|
|
35
31
|
}
|
|
36
32
|
export declare function runPlatformPermissionStoreConformance(opts: PlatformPermissionStoreConformanceOptions): void;
|
|
37
|
-
//# sourceMappingURL=platformPermissionStoreSuite.d.ts.map
|