@jskit-ai/auth-core 0.1.243 → 0.1.245

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/auth-core",
3
- "version": "0.1.243",
3
+ "version": "0.1.245",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test --test-concurrency=1"
@@ -135,6 +135,6 @@
135
135
  }
136
136
  },
137
137
  "peerDependencies": {
138
- "@jskit-ai/kernel": "0.1.245"
138
+ "@jskit-ai/kernel": "0.1.247"
139
139
  }
140
140
  }
@@ -1,7 +1,7 @@
1
1
  import { createSchema } from "json-rest-schema";
2
2
  import {
3
3
  emptyInputValidator
4
- } from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
4
+ } from "@jskit-ai/kernel/server/actions/actionContributorHelpers";
5
5
  import {
6
6
  composeSchemaDefinitions
7
7
  } from "@jskit-ai/kernel/shared/validators";
@@ -54,10 +54,6 @@ const authActionSpecifications = Object.freeze([
54
54
  surfacesFrom: "enabled",
55
55
  input: authRegisterCommand.operation.body,
56
56
  idempotency: "none",
57
- audit: {
58
- actionName: "auth.register"
59
- },
60
- observability: {},
61
57
  async run(authService, input) {
62
58
  return authService.register(input);
63
59
  }
@@ -70,10 +66,6 @@ const authActionSpecifications = Object.freeze([
70
66
  surfacesFrom: "enabled",
71
67
  input: authRegisterConfirmationResendCommand.operation.body,
72
68
  idempotency: "none",
73
- audit: {
74
- actionName: "auth.register.confirmation.resend"
75
- },
76
- observability: {},
77
69
  async run(authService, input) {
78
70
  return authService.resendRegisterConfirmation(input);
79
71
  }
@@ -86,10 +78,6 @@ const authActionSpecifications = Object.freeze([
86
78
  surfacesFrom: "enabled",
87
79
  input: authLoginPasswordCommand.operation.body,
88
80
  idempotency: "none",
89
- audit: {
90
- actionName: "auth.login.password"
91
- },
92
- observability: {},
93
81
  async run(authService, input) {
94
82
  return authService.login(input);
95
83
  }
@@ -102,10 +90,6 @@ const authActionSpecifications = Object.freeze([
102
90
  surfacesFrom: "enabled",
103
91
  input: authLoginOtpRequestCommand.operation.body,
104
92
  idempotency: "none",
105
- audit: {
106
- actionName: "auth.login.otp.request"
107
- },
108
- observability: {},
109
93
  async run(authService, input) {
110
94
  return authService.requestOtpLogin(input);
111
95
  }
@@ -118,10 +102,6 @@ const authActionSpecifications = Object.freeze([
118
102
  surfacesFrom: "enabled",
119
103
  input: authLoginOtpVerifyCommand.operation.body,
120
104
  idempotency: "none",
121
- audit: {
122
- actionName: "auth.login.otp.verify"
123
- },
124
- observability: {},
125
105
  async run(authService, input) {
126
106
  return authService.verifyOtpLogin(input);
127
107
  }
@@ -134,10 +114,6 @@ const authActionSpecifications = Object.freeze([
134
114
  surfacesFrom: "enabled",
135
115
  input: authLoginOAuthStartInput,
136
116
  idempotency: "none",
137
- audit: {
138
- actionName: "auth.login.oauth.start"
139
- },
140
- observability: {},
141
117
  async run(authService, input, context) {
142
118
  return authService.oauthStart(input, context?.requestMeta?.request || null);
143
119
  }
@@ -150,10 +126,6 @@ const authActionSpecifications = Object.freeze([
150
126
  surfacesFrom: "enabled",
151
127
  input: authLoginOAuthCompleteCommand.operation.body,
152
128
  idempotency: "none",
153
- audit: {
154
- actionName: "auth.login.oauth.complete"
155
- },
156
- observability: {},
157
129
  async run(authService, input, context) {
158
130
  return authService.oauthComplete(input, context?.requestMeta?.request || null);
159
131
  }
@@ -166,10 +138,6 @@ const authActionSpecifications = Object.freeze([
166
138
  surfacesFrom: "enabled",
167
139
  input: authDevLoginAsCommand.operation.body,
168
140
  idempotency: "none",
169
- audit: {
170
- actionName: "auth.dev.loginAs"
171
- },
172
- observability: {},
173
141
  async run(authService, input, context) {
174
142
  return authService.devLoginAs(
175
143
  requireRequestContext(context, "auth.dev.loginAs"),
@@ -185,10 +153,6 @@ const authActionSpecifications = Object.freeze([
185
153
  surfacesFrom: "enabled",
186
154
  input: authPasswordResetRequestCommand.operation.body,
187
155
  idempotency: "none",
188
- audit: {
189
- actionName: "auth.password.reset.request"
190
- },
191
- observability: {},
192
156
  async run(authService, input) {
193
157
  return authService.requestPasswordReset(input);
194
158
  }
@@ -201,10 +165,6 @@ const authActionSpecifications = Object.freeze([
201
165
  surfacesFrom: "enabled",
202
166
  input: authPasswordRecoveryCompleteCommand.operation.body,
203
167
  idempotency: "none",
204
- audit: {
205
- actionName: "auth.password.recovery.complete"
206
- },
207
- observability: {},
208
168
  async run(authService, input) {
209
169
  return authService.completePasswordRecovery(input);
210
170
  }
@@ -217,10 +177,6 @@ const authActionSpecifications = Object.freeze([
217
177
  surfacesFrom: "enabled",
218
178
  input: authPasswordResetCommand.operation.body,
219
179
  idempotency: "none",
220
- audit: {
221
- actionName: "auth.password.reset"
222
- },
223
- observability: {},
224
180
  async run(authService, input, context) {
225
181
  return authService.resetPassword(requireRequestContext(context, "auth.password.reset"), input);
226
182
  }
@@ -234,10 +190,6 @@ const authActionSpecifications = Object.freeze([
234
190
  input: emptyInputValidator,
235
191
  output: authLogoutOutput,
236
192
  idempotency: "none",
237
- audit: {
238
- actionName: "auth.logout"
239
- },
240
- observability: {},
241
193
  async run(authService, _input, context) {
242
194
  let logoutResult = {
243
195
  ok: true,
@@ -260,10 +212,6 @@ const authActionSpecifications = Object.freeze([
260
212
  surfacesFrom: "enabled",
261
213
  input: emptyInputValidator,
262
214
  idempotency: "none",
263
- audit: {
264
- actionName: "auth.session.read"
265
- },
266
- observability: {},
267
215
  async run(authService, _input, context) {
268
216
  return authService.authenticateRequest(requireRequestContext(context, "auth.session.read"));
269
217
  }
@@ -13,6 +13,7 @@ function defaultHasPermission({ permission, permissions = [] } = {}) {
13
13
  }
14
14
 
15
15
  function createPolicy({ authService, extensions, env }) {
16
+ const unsafeMethods = parseList(env.AUTH_CSRF_UNSAFE_METHODS);
16
17
  return Object.freeze({
17
18
  async resolveActor(request) {
18
19
  if (authService && typeof authService.authenticateRequest === "function") {
@@ -27,7 +28,8 @@ function createPolicy({ authService, extensions, env }) {
27
28
  options: Object.freeze({
28
29
  nodeEnv: String(env.NODE_ENV || "development").trim() || "development",
29
30
  apiPrefix: String(env.AUTH_API_PREFIX || "/api/").trim() || "/api/",
30
- unsafeMethods: Object.freeze(parseList(env.AUTH_CSRF_UNSAFE_METHODS)),
31
+ // Unset AUTH_CSRF_UNSAFE_METHODS keeps the plugin default (POST, PUT, PATCH, DELETE).
32
+ unsafeMethods: unsafeMethods.length > 0 ? Object.freeze(unsafeMethods) : undefined,
31
33
  // Unset AUTH_CSRF_COOKIE_SECURE follows NODE_ENV (Secure in production).
32
34
  csrfCookieOpts: Object.freeze(
33
35
  String(env.AUTH_CSRF_COOKIE_SECURE ?? "").trim()
@@ -1,5 +1,5 @@
1
1
  import { createSchema } from "json-rest-schema";
2
- import { recordIdInputSchema } from "@jskit-ai/kernel/shared/validators";
2
+ import { recordIdSchema } from "@jskit-ai/kernel/shared/validators";
3
3
  import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
4
4
  import {
5
5
  authEmailValidator,
@@ -24,7 +24,7 @@ const AUTH_DEV_LOGIN_AS_MESSAGES = createCommandMessages({
24
24
  const authDevLoginAsBodyValidator = deepFreeze({
25
25
  schema: createSchema({
26
26
  userId: {
27
- ...recordIdInputSchema,
27
+ ...recordIdSchema,
28
28
  required: false
29
29
  },
30
30
  email: {
@@ -1,6 +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
+ import { resolveSchemaTransportSchemaDefinition } from "@jskit-ai/kernel/shared/validators";
4
4
  import { authRegisterCommand } from "../src/shared/commands/authRegisterCommand.js";
5
5
  import { authRegisterConfirmationResendCommand } from "../src/shared/commands/authRegisterConfirmationResendCommand.js";
6
6
  import { authLoginPasswordCommand } from "../src/shared/commands/authLoginPasswordCommand.js";
@@ -36,7 +36,7 @@ test("auth commands expose canonical operation validator messages", () => {
36
36
  });
37
37
 
38
38
  test("oauth complete command allows provider-less session-pair callbacks", () => {
39
- const bodySchema = resolveStructuredSchemaTransportSchema(
39
+ const bodySchema = resolveSchemaTransportSchemaDefinition(
40
40
  authLoginOAuthCompleteCommand.operation.body,
41
41
  {
42
42
  context: "auth login oauth complete body",
@@ -46,7 +46,7 @@ test("oauth complete command allows provider-less session-pair callbacks", () =>
46
46
  const bodyRequired = Array.isArray(bodySchema?.required) ? bodySchema.required : [];
47
47
  assert.equal(bodyRequired.includes("provider"), false);
48
48
 
49
- const responseSchema = resolveStructuredSchemaTransportSchema(
49
+ const responseSchema = resolveSchemaTransportSchemaDefinition(
50
50
  authLoginOAuthCompleteCommand.operation.response,
51
51
  {
52
52
  context: "auth login oauth complete response",
@@ -78,21 +78,21 @@ test("auth command body validators lowercase email inputs", () => {
78
78
  });
79
79
 
80
80
  test("auth session and oauth start commands expose explicit nested response schemas", () => {
81
- const sessionResponseSchema = resolveStructuredSchemaTransportSchema(
81
+ const sessionResponseSchema = resolveSchemaTransportSchemaDefinition(
82
82
  authSessionReadCommand.operation.response,
83
83
  {
84
84
  context: "auth session read response",
85
85
  defaultMode: "replace"
86
86
  }
87
87
  );
88
- const sessionUnavailableSchema = resolveStructuredSchemaTransportSchema(
88
+ const sessionUnavailableSchema = resolveSchemaTransportSchemaDefinition(
89
89
  authSessionReadCommand.operation.unavailableResponse,
90
90
  {
91
91
  context: "auth session read unavailable response",
92
92
  defaultMode: "replace"
93
93
  }
94
94
  );
95
- const oauthStartResponseSchema = resolveStructuredSchemaTransportSchema(
95
+ const oauthStartResponseSchema = resolveSchemaTransportSchemaDefinition(
96
96
  authLoginOAuthStartCommand.operation.response,
97
97
  {
98
98
  context: "auth login oauth start response",
@@ -1,5 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
+ import Fastify from "fastify";
3
4
  import { createActionProvider } from "@jskit-ai/kernel/server/actions";
4
5
  import { createCapabilityRuntime, defineProvider } from "@jskit-ai/kernel/shared/capabilities";
5
6
  import { HttpProvider } from "@jskit-ai/kernel/server/http";
@@ -126,3 +127,23 @@ test("auth policy denies protected routes when no auth service is installed", as
126
127
  routeOptions: { config: { authPolicy: "required" } }
127
128
  }, {}), /Authentication required/);
128
129
  });
130
+
131
+ test("AuthPolicyProvider checks CSRF on unsafe methods when AUTH_CSRF_UNSAFE_METHODS is unset", async (t) => {
132
+ const app = Fastify();
133
+ t.after(() => app.close());
134
+ const runtime = createCapabilityRuntime({
135
+ inputs: runtimeInputs(app),
136
+ providers: [
137
+ createActionProvider(),
138
+ HttpProvider,
139
+ AuthExtensionsProvider,
140
+ AuthFeature,
141
+ AuthPolicyProvider
142
+ ]
143
+ });
144
+ await runtime.start();
145
+ app.post("/api/write", { config: { authPolicy: "public" } }, () => ({ accepted: true }));
146
+
147
+ const missingToken = await app.inject({ method: "POST", url: "/api/write" });
148
+ assert.equal(missingToken.statusCode, 403);
149
+ });