@jskit-ai/auth-core 0.1.54 → 0.1.56

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.
@@ -1,10 +1,10 @@
1
- import { Type } from "typebox";
2
- import { normalizeObjectInput } from "../inputNormalization.js";
1
+ import { createSchema } from "json-rest-schema";
2
+ import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
3
3
  import {
4
- authEmailValidator,
5
- authPasswordValidator,
4
+ authEmailFieldDefinition,
5
+ authPasswordFieldDefinition,
6
6
  createCommandMessages,
7
- registerResponseValidator
7
+ registerOutputValidator
8
8
  } from "./authCommandValidators.js";
9
9
 
10
10
  const AUTH_REGISTER_MESSAGES = createCommandMessages({
@@ -23,35 +23,30 @@ const AUTH_REGISTER_MESSAGES = createCommandMessages({
23
23
  }
24
24
  });
25
25
 
26
- const authRegisterBodyValidator = Object.freeze({
27
- schema: Type.Object(
28
- {
29
- email: authEmailValidator.schema,
30
- password: authPasswordValidator.schema
31
- },
32
- {
33
- additionalProperties: false
34
- }
35
- ),
36
- normalize: normalizeObjectInput,
26
+ const authRegisterBodyValidator = deepFreeze({
27
+ schema: createSchema({
28
+ email: { ...authEmailFieldDefinition, required: true },
29
+ password: { ...authPasswordFieldDefinition, required: true }
30
+ }),
31
+ mode: "create",
37
32
  messages: AUTH_REGISTER_MESSAGES
38
33
  });
39
34
 
40
- const authRegisterCommand = Object.freeze({
35
+ const authRegisterCommand = deepFreeze({
41
36
  command: "auth.register",
42
- operation: Object.freeze({
37
+ operation: {
43
38
  method: "POST",
44
- bodyValidator: authRegisterBodyValidator,
45
- responseValidator: registerResponseValidator,
39
+ body: authRegisterBodyValidator,
40
+ response: registerOutputValidator,
46
41
  messages: AUTH_REGISTER_MESSAGES,
47
42
  idempotent: false,
48
- invalidates: Object.freeze(["auth.session.read"])
49
- })
43
+ invalidates: ["auth.session.read"]
44
+ }
50
45
  });
51
46
 
52
47
  export {
53
48
  authRegisterBodyValidator,
54
- registerResponseValidator,
49
+ registerOutputValidator,
55
50
  AUTH_REGISTER_MESSAGES,
56
51
  authRegisterCommand
57
52
  };
@@ -1,9 +1,9 @@
1
- import { Type } from "typebox";
2
- import { normalizeObjectInput } from "../inputNormalization.js";
1
+ import { createSchema } from "json-rest-schema";
2
+ import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
3
3
  import {
4
- authEmailValidator,
4
+ authEmailFieldDefinition,
5
5
  createCommandMessages,
6
- okMessageResponseValidator
6
+ okMessageOutputValidator
7
7
  } from "./authCommandValidators.js";
8
8
 
9
9
  const AUTH_REGISTER_CONFIRMATION_RESEND_MESSAGES = createCommandMessages({
@@ -17,29 +17,24 @@ const AUTH_REGISTER_CONFIRMATION_RESEND_MESSAGES = createCommandMessages({
17
17
  }
18
18
  });
19
19
 
20
- const authRegisterConfirmationResendBodyValidator = Object.freeze({
21
- schema: Type.Object(
22
- {
23
- email: authEmailValidator.schema
24
- },
25
- {
26
- additionalProperties: false
27
- }
28
- ),
29
- normalize: normalizeObjectInput,
20
+ const authRegisterConfirmationResendBodyValidator = deepFreeze({
21
+ schema: createSchema({
22
+ email: { ...authEmailFieldDefinition, required: true }
23
+ }),
24
+ mode: "create",
30
25
  messages: AUTH_REGISTER_CONFIRMATION_RESEND_MESSAGES
31
26
  });
32
27
 
33
- const authRegisterConfirmationResendCommand = Object.freeze({
28
+ const authRegisterConfirmationResendCommand = deepFreeze({
34
29
  command: "auth.register.confirmation.resend",
35
- operation: Object.freeze({
30
+ operation: {
36
31
  method: "POST",
37
- bodyValidator: authRegisterConfirmationResendBodyValidator,
38
- responseValidator: okMessageResponseValidator,
32
+ body: authRegisterConfirmationResendBodyValidator,
33
+ response: okMessageOutputValidator,
39
34
  messages: AUTH_REGISTER_CONFIRMATION_RESEND_MESSAGES,
40
35
  idempotent: false,
41
- invalidates: Object.freeze([])
42
- })
36
+ invalidates: []
37
+ }
43
38
  });
44
39
 
45
40
  export {
@@ -1,26 +1,27 @@
1
1
  import {
2
2
  createCommandMessages,
3
- sessionResponseValidator,
4
- sessionUnavailableResponseValidator
3
+ sessionOutputValidator,
4
+ sessionUnavailableOutputValidator
5
5
  } from "./authCommandValidators.js";
6
+ import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
6
7
 
7
8
  const AUTH_SESSION_READ_MESSAGES = createCommandMessages();
8
9
 
9
- const authSessionReadCommand = Object.freeze({
10
+ const authSessionReadCommand = deepFreeze({
10
11
  command: "auth.session.read",
11
- operation: Object.freeze({
12
+ operation: {
12
13
  method: "GET",
13
- responseValidator: sessionResponseValidator,
14
- unavailableResponseValidator: sessionUnavailableResponseValidator,
14
+ response: sessionOutputValidator,
15
+ unavailableResponse: sessionUnavailableOutputValidator,
15
16
  messages: AUTH_SESSION_READ_MESSAGES,
16
17
  idempotent: true,
17
- invalidates: Object.freeze([])
18
- })
18
+ invalidates: []
19
+ }
19
20
  });
20
21
 
21
22
  export {
22
- sessionResponseValidator,
23
- sessionUnavailableResponseValidator,
23
+ sessionOutputValidator,
24
+ sessionUnavailableOutputValidator,
24
25
  AUTH_SESSION_READ_MESSAGES,
25
26
  authSessionReadCommand
26
27
  };
@@ -5,7 +5,8 @@ import {
5
5
  AUTH_POLICY_CONTEXT_RESOLVER_TAG,
6
6
  composeAuthPolicyContextResolvers,
7
7
  registerAuthPolicyContextResolver,
8
- resolveAuthPolicyContextResolvers
8
+ resolveAuthPolicyContextResolvers,
9
+ resolveComposedAuthPolicyContextResolver
9
10
  } from "../src/server/authPolicyContextResolverRegistry.js";
10
11
 
11
12
  test("auth policy context resolver registry resolves resolvers in order", async () => {
@@ -54,3 +55,40 @@ test("auth policy context resolver registry resolves resolvers in order", async
54
55
  test("auth policy context resolver registry exports canonical tag", () => {
55
56
  assert.equal(AUTH_POLICY_CONTEXT_RESOLVER_TAG, "jskit.auth.policy.context.resolvers");
56
57
  });
58
+
59
+ test("auth policy context resolver registry resolves composed resolver directly from scope", async () => {
60
+ const app = createApplication();
61
+
62
+ registerAuthPolicyContextResolver(app, "test.auth.policy.context.permissions", () => ({
63
+ resolverId: "permissions",
64
+ order: 20,
65
+ async resolveAuthPolicyContext() {
66
+ return {
67
+ permissions: ["alpha.read"]
68
+ };
69
+ }
70
+ }));
71
+
72
+ registerAuthPolicyContextResolver(app, "test.auth.policy.context.workspace", () => ({
73
+ resolverId: "workspace",
74
+ order: 10,
75
+ async resolveAuthPolicyContext() {
76
+ return {
77
+ workspace: { id: "11" },
78
+ membership: { roleSid: "member" },
79
+ permissions: ["workspace.read"]
80
+ };
81
+ }
82
+ }));
83
+
84
+ const resolveContext = resolveComposedAuthPolicyContextResolver(app);
85
+ const context = await resolveContext({
86
+ actor: { id: "7" }
87
+ });
88
+
89
+ assert.deepEqual(context, {
90
+ workspace: { id: "11" },
91
+ membership: { roleSid: "member" },
92
+ permissions: ["workspace.read", "alpha.read"]
93
+ });
94
+ });
@@ -1,5 +1,6 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
+ import { resolveStructuredSchemaTransportSchema } from "@jskit-ai/kernel/shared/validators";
3
4
  import { authRegisterCommand } from "../src/shared/commands/authRegisterCommand.js";
4
5
  import { authRegisterConfirmationResendCommand } from "../src/shared/commands/authRegisterConfirmationResendCommand.js";
5
6
  import { authLoginPasswordCommand } from "../src/shared/commands/authLoginPasswordCommand.js";
@@ -35,11 +36,76 @@ test("auth commands expose canonical operation validator messages", () => {
35
36
  });
36
37
 
37
38
  test("oauth complete command allows provider-less session-pair callbacks", () => {
38
- const bodySchema = authLoginOAuthCompleteCommand.operation.bodyValidator.schema;
39
+ const bodySchema = resolveStructuredSchemaTransportSchema(
40
+ authLoginOAuthCompleteCommand.operation.body,
41
+ {
42
+ context: "auth login oauth complete body",
43
+ defaultMode: "patch"
44
+ }
45
+ );
39
46
  const bodyRequired = Array.isArray(bodySchema?.required) ? bodySchema.required : [];
40
47
  assert.equal(bodyRequired.includes("provider"), false);
41
48
 
42
- const responseSchema = authLoginOAuthCompleteCommand.operation.responseValidator.schema;
49
+ const responseSchema = resolveStructuredSchemaTransportSchema(
50
+ authLoginOAuthCompleteCommand.operation.response,
51
+ {
52
+ context: "auth login oauth complete response",
53
+ defaultMode: "replace"
54
+ }
55
+ );
43
56
  const responseRequired = Array.isArray(responseSchema?.required) ? responseSchema.required : [];
44
57
  assert.equal(responseRequired.includes("provider"), false);
45
58
  });
59
+
60
+ test("auth command body validators lowercase email inputs", () => {
61
+ const commands = [
62
+ authRegisterCommand,
63
+ authRegisterConfirmationResendCommand,
64
+ authLoginPasswordCommand,
65
+ authLoginOtpRequestCommand,
66
+ authLoginOtpVerifyCommand,
67
+ authPasswordResetRequestCommand
68
+ ];
69
+
70
+ for (const command of commands) {
71
+ const result = command.operation.body.schema.patch({
72
+ email: " ADA@EXAMPLE.COM "
73
+ });
74
+ if (Object.hasOwn(result.validatedObject, "email")) {
75
+ assert.equal(result.validatedObject.email, "ada@example.com");
76
+ }
77
+ }
78
+ });
79
+
80
+ test("auth session and oauth start commands expose explicit nested response schemas", () => {
81
+ const sessionResponseSchema = resolveStructuredSchemaTransportSchema(
82
+ authSessionReadCommand.operation.response,
83
+ {
84
+ context: "auth session read response",
85
+ defaultMode: "replace"
86
+ }
87
+ );
88
+ const sessionUnavailableSchema = resolveStructuredSchemaTransportSchema(
89
+ authSessionReadCommand.operation.unavailableResponse,
90
+ {
91
+ context: "auth session read unavailable response",
92
+ defaultMode: "replace"
93
+ }
94
+ );
95
+ const oauthStartResponseSchema = resolveStructuredSchemaTransportSchema(
96
+ authLoginOAuthStartCommand.operation.response,
97
+ {
98
+ context: "auth login oauth start response",
99
+ defaultMode: "replace"
100
+ }
101
+ );
102
+
103
+ assert.equal(sessionResponseSchema.properties.oauthProviders.type, "array");
104
+ assert.equal(sessionResponseSchema.properties.oauthProviders.items["x-json-rest-schema"]?.castType, "object");
105
+ assert.equal(Array.isArray(sessionResponseSchema.properties.oauthProviders.items.allOf), true);
106
+ assert.match(sessionResponseSchema.properties.oauthProviders.items.allOf[0]?.$ref || "", /^#\/definitions\//);
107
+ assert.equal(Array.isArray(sessionUnavailableSchema.properties.oauthDefaultProvider.anyOf), true);
108
+ assert.equal(oauthStartResponseSchema.properties.provider.type, "string");
109
+ assert.equal(oauthStartResponseSchema.properties.returnTo.type, "string");
110
+ assert.equal(oauthStartResponseSchema.properties.url.type, "string");
111
+ });
@@ -50,6 +50,8 @@ test("FastifyAuthPolicyServiceProvider registers auth policy plugin through prov
50
50
 
51
51
  test("FastifyAuthPolicyServiceProvider wires optional auth policy context resolver", async () => {
52
52
  const { fastify, state } = createFakeFastifyPolicyRuntime();
53
+ const makeCalls = [];
54
+ const resolveTagCalls = [];
53
55
  const bag = new Map([
54
56
  ["jskit.fastify", fastify],
55
57
  ["jskit.env", { NODE_ENV: "test" }],
@@ -65,14 +67,6 @@ test("FastifyAuthPolicyServiceProvider wires optional auth policy context resolv
65
67
  };
66
68
  }
67
69
  }
68
- ],
69
- [
70
- "auth.policy.contextResolver",
71
- async ({ actor, request }) => ({
72
- workspace: { id: 11, slug: String(request?.params?.workspaceSlug || "").toLowerCase() },
73
- membership: { roleSid: "member" },
74
- permissions: actor?.id === 7 ? ["projects.read"] : []
75
- })
76
70
  ]
77
71
  ]);
78
72
 
@@ -81,17 +75,30 @@ test("FastifyAuthPolicyServiceProvider wires optional auth policy context resolv
81
75
  return bag.has(token);
82
76
  },
83
77
  make(token) {
78
+ makeCalls.push(String(token));
84
79
  if (!bag.has(token)) {
85
80
  throw new Error(`Missing token ${String(token)}`);
86
81
  }
87
82
  return bag.get(token);
88
83
  },
89
84
  resolveTag(tag) {
85
+ resolveTagCalls.push(String(tag));
90
86
  if (tag !== AUTH_POLICY_CONTEXT_RESOLVER_TAG) {
91
87
  return [];
92
88
  }
93
89
 
94
90
  return [
91
+ {
92
+ resolverId: "workspace",
93
+ order: 10,
94
+ async resolveAuthPolicyContext({ actor, request }) {
95
+ return {
96
+ workspace: { id: 11, slug: String(request?.params?.workspaceSlug || "").toLowerCase() },
97
+ membership: { roleSid: "member" },
98
+ permissions: actor?.id === 7 ? ["projects.read"] : []
99
+ };
100
+ }
101
+ },
95
102
  async () => ({
96
103
  permissions: ["settings.manage"]
97
104
  })
@@ -103,6 +110,9 @@ test("FastifyAuthPolicyServiceProvider wires optional auth policy context resolv
103
110
  provider.register(app);
104
111
  await provider.boot(app);
105
112
 
113
+ assert.deepEqual(makeCalls, ["jskit.env", "jskit.fastify"]);
114
+ assert.deepEqual(resolveTagCalls, []);
115
+
106
116
  const request = {
107
117
  method: "GET",
108
118
  raw: { url: "/api/w/acme/projects" },
@@ -117,6 +127,8 @@ test("FastifyAuthPolicyServiceProvider wires optional auth policy context resolv
117
127
  };
118
128
 
119
129
  await state.preHandler(request, {});
130
+ assert.ok(makeCalls.includes("authService"));
131
+ assert.deepEqual(resolveTagCalls, [AUTH_POLICY_CONTEXT_RESOLVER_TAG]);
120
132
  assert.equal(request.workspace?.id, 11);
121
133
  assert.equal(request.workspace?.slug, "acme");
122
134
  assert.equal(request.membership?.roleSid, "member");