@jskit-ai/auth-core 0.1.8 → 0.1.10

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,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  "packageVersion": 1,
3
3
  "packageId": "@jskit-ai/auth-core",
4
- "version": "0.1.8",
4
+ "version": "0.1.10",
5
5
  "dependsOn": [
6
6
  "@jskit-ai/value-app-config-shared"
7
7
  ],
@@ -68,7 +68,7 @@ export default Object.freeze({
68
68
  "mutations": {
69
69
  "dependencies": {
70
70
  "runtime": {
71
- "@jskit-ai/kernel": "0.1.8",
71
+ "@jskit-ai/kernel": "0.1.10",
72
72
  "@fastify/cookie": "^11.0.2",
73
73
  "@fastify/csrf-protection": "^7.1.0",
74
74
  "@fastify/rate-limit": "^10.3.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/auth-core",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -29,7 +29,9 @@
29
29
  "./shared/oauthCallbackParams": "./src/shared/oauthCallbackParams.js",
30
30
  "./shared/inputNormalization": "./src/shared/inputNormalization.js",
31
31
  "./shared/inviteTokens": "./src/shared/inviteTokens.js",
32
+ "./shared/commands": "./src/shared/commands/index.js",
32
33
  "./shared/commands/authRegisterCommand": "./src/shared/commands/authRegisterCommand.js",
34
+ "./shared/commands/authRegisterConfirmationResendCommand": "./src/shared/commands/authRegisterConfirmationResendCommand.js",
33
35
  "./shared/commands/authLoginPasswordCommand": "./src/shared/commands/authLoginPasswordCommand.js",
34
36
  "./shared/commands/authLoginOtpRequestCommand": "./src/shared/commands/authLoginOtpRequestCommand.js",
35
37
  "./shared/commands/authLoginOtpVerifyCommand": "./src/shared/commands/authLoginOtpVerifyCommand.js",
@@ -42,7 +44,7 @@
42
44
  "./shared/commands/authSessionReadCommand": "./src/shared/commands/authSessionReadCommand.js"
43
45
  },
44
46
  "dependencies": {
45
- "@jskit-ai/kernel": "0.1.8",
47
+ "@jskit-ai/kernel": "0.1.10",
46
48
  "@fastify/cookie": "^11.0.2",
47
49
  "@fastify/csrf-protection": "^7.1.0",
48
50
  "@fastify/rate-limit": "^10.3.0",
@@ -1,16 +1,12 @@
1
1
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
-
3
- function normalizePermissions(value) {
4
- const source = Array.isArray(value) ? value : [];
5
- return source.map((entry) => normalizeText(entry)).filter(Boolean);
6
- }
2
+ import { normalizePermissionList } from "@jskit-ai/kernel/shared/support/permissions";
7
3
 
8
4
  function createAuthActionContextContributor() {
9
5
  return Object.freeze({
10
6
  contributorId: "auth.policy.request-context",
11
7
  contribute({ request } = {}) {
12
8
  const contribution = {};
13
- const permissions = normalizePermissions(request?.permissions);
9
+ const permissions = normalizePermissionList(request?.permissions);
14
10
 
15
11
  if (request?.user) {
16
12
  contribution.actor = request.user;
@@ -23,10 +23,6 @@ function normalizeMembershipForAccess(membershipLike) {
23
23
  };
24
24
  }
25
25
 
26
- function mapMembershipSummary(membershipLike) {
27
- return normalizeMembershipForAccess(membershipLike);
28
- }
29
-
30
26
  function normalizePermissions(value) {
31
27
  if (!Array.isArray(value)) {
32
28
  return [];
@@ -61,7 +57,6 @@ export {
61
57
  resolveMembershipRoleId,
62
58
  resolveMembershipStatus,
63
59
  normalizeMembershipForAccess,
64
- mapMembershipSummary,
65
60
  normalizePermissions,
66
61
  createMembershipIndexes
67
62
  };
@@ -93,69 +93,77 @@ function resetPassword(rawPassword) {
93
93
  return registerPassword(rawPassword);
94
94
  }
95
95
 
96
- function registerInput(payload = {}) {
97
- const emailCheck = validateEmail(payload.email);
98
- const passwordCheck = registerPassword(payload.password);
96
+ function buildFieldErrors(entries = []) {
99
97
  const fieldErrors = {};
100
-
101
- if (emailCheck.error) {
102
- fieldErrors.email = emailCheck.error;
103
- }
104
- if (passwordCheck.error) {
105
- fieldErrors.password = passwordCheck.error;
98
+ for (const entry of Array.isArray(entries) ? entries : []) {
99
+ const field = String(entry?.field || "").trim();
100
+ const error = String(entry?.error || "").trim();
101
+ if (!field || !error) {
102
+ continue;
103
+ }
104
+ fieldErrors[field] = error;
106
105
  }
107
-
108
- return {
109
- email: emailCheck.email,
110
- password: passwordCheck.password,
111
- fieldErrors
112
- };
106
+ return fieldErrors;
113
107
  }
114
108
 
115
- function loginInput(payload = {}) {
109
+ function buildEmailPasswordInput(payload = {}, { passwordValidator } = {}) {
116
110
  const emailCheck = validateEmail(payload.email);
117
- const passwordCheck = loginPassword(payload.password);
118
- const fieldErrors = {};
119
-
120
- if (emailCheck.error) {
121
- fieldErrors.email = emailCheck.error;
122
- }
123
- if (passwordCheck.error) {
124
- fieldErrors.password = passwordCheck.error;
125
- }
111
+ const resolvePassword = typeof passwordValidator === "function" ? passwordValidator : registerPassword;
112
+ const passwordCheck = resolvePassword(payload.password);
126
113
 
127
114
  return {
128
115
  email: emailCheck.email,
129
116
  password: passwordCheck.password,
130
- fieldErrors
117
+ fieldErrors: buildFieldErrors([
118
+ {
119
+ field: "email",
120
+ error: emailCheck.error
121
+ },
122
+ {
123
+ field: "password",
124
+ error: passwordCheck.error
125
+ }
126
+ ])
131
127
  };
132
128
  }
133
129
 
130
+ function registerInput(payload = {}) {
131
+ return buildEmailPasswordInput(payload, {
132
+ passwordValidator: registerPassword
133
+ });
134
+ }
135
+
136
+ function loginInput(payload = {}) {
137
+ return buildEmailPasswordInput(payload, {
138
+ passwordValidator: loginPassword
139
+ });
140
+ }
141
+
134
142
  function forgotPasswordInput(payload = {}) {
135
143
  const emailCheck = validateEmail(payload.email);
136
- const fieldErrors = {};
137
-
138
- if (emailCheck.error) {
139
- fieldErrors.email = emailCheck.error;
140
- }
141
144
 
142
145
  return {
143
146
  email: emailCheck.email,
144
- fieldErrors
147
+ fieldErrors: buildFieldErrors([
148
+ {
149
+ field: "email",
150
+ error: emailCheck.error
151
+ }
152
+ ])
145
153
  };
146
154
  }
147
155
 
148
156
  function resetPasswordInput(payload = {}) {
149
157
  const passwordCheck = resetPassword(payload.password);
150
- const fieldErrors = {};
151
-
152
- if (passwordCheck.error) {
153
- fieldErrors.password = passwordCheck.error;
154
- }
155
158
 
156
159
  return {
157
160
  password: passwordCheck.password,
158
- fieldErrors
161
+ fieldErrors: buildFieldErrors([
162
+ {
163
+ field: "password",
164
+ error: passwordCheck.error
165
+ }
166
+ ])
159
167
  };
160
168
  }
161
169
 
@@ -8,6 +8,9 @@ function createApi({ request }) {
8
8
  register(payload) {
9
9
  return request(AUTH_PATHS.REGISTER, { method: "POST", body: payload });
10
10
  },
11
+ resendRegisterConfirmation(payload) {
12
+ return request(AUTH_PATHS.REGISTER_CONFIRMATION_RESEND, { method: "POST", body: payload });
13
+ },
11
14
  login(payload) {
12
15
  return request(AUTH_PATHS.LOGIN, { method: "POST", body: payload });
13
16
  },
@@ -1,5 +1,6 @@
1
1
  const AUTH_PATHS = Object.freeze({
2
2
  REGISTER: "/api/register",
3
+ REGISTER_CONFIRMATION_RESEND: "/api/register/confirmation/resend",
3
4
  LOGIN: "/api/login",
4
5
  LOGIN_OTP_REQUEST: "/api/login/otp/request",
5
6
  LOGIN_OTP_VERIFY: "/api/login/otp/verify",
@@ -152,7 +152,7 @@ const oauthCompleteResponseValidator = Object.freeze({
152
152
  schema: Type.Object(
153
153
  {
154
154
  ok: Type.Boolean(),
155
- provider: oauthProviderValidator.schema,
155
+ provider: Type.Optional(oauthProviderValidator.schema),
156
156
  username: Type.String({ minLength: 1, maxLength: 120 }),
157
157
  email: authEmailValidator.schema
158
158
  },
@@ -12,7 +12,6 @@ import {
12
12
  const AUTH_LOGIN_OAUTH_COMPLETE_MESSAGES = createCommandMessages({
13
13
  fields: {
14
14
  provider: {
15
- required: "OAuth provider is required.",
16
15
  pattern: "OAuth provider id is invalid.",
17
16
  default: "OAuth provider id is invalid."
18
17
  },
@@ -31,7 +30,7 @@ const AUTH_LOGIN_OAUTH_COMPLETE_MESSAGES = createCommandMessages({
31
30
  const authLoginOAuthCompleteBodyValidator = Object.freeze({
32
31
  schema: Type.Object(
33
32
  {
34
- provider: oauthProviderValidator.schema,
33
+ provider: Type.Optional(oauthProviderValidator.schema),
35
34
  code: Type.Optional(authRecoveryTokenValidator.schema),
36
35
  accessToken: Type.Optional(authAccessTokenValidator.schema),
37
36
  refreshToken: Type.Optional(authRefreshTokenValidator.schema),
@@ -0,0 +1,49 @@
1
+ import { Type } from "typebox";
2
+ import { normalizeObjectInput } from "../inputNormalization.js";
3
+ import {
4
+ authEmailValidator,
5
+ createCommandMessages,
6
+ okMessageResponseValidator
7
+ } from "./authCommandValidators.js";
8
+
9
+ const AUTH_REGISTER_CONFIRMATION_RESEND_MESSAGES = createCommandMessages({
10
+ fields: {
11
+ email: {
12
+ required: "Email is required.",
13
+ minLength: "Email is required.",
14
+ pattern: "Enter a valid email address.",
15
+ default: "Enter a valid email address."
16
+ }
17
+ }
18
+ });
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,
30
+ messages: AUTH_REGISTER_CONFIRMATION_RESEND_MESSAGES
31
+ });
32
+
33
+ const authRegisterConfirmationResendCommand = Object.freeze({
34
+ command: "auth.register.confirmation.resend",
35
+ operation: Object.freeze({
36
+ method: "POST",
37
+ bodyValidator: authRegisterConfirmationResendBodyValidator,
38
+ responseValidator: okMessageResponseValidator,
39
+ messages: AUTH_REGISTER_CONFIRMATION_RESEND_MESSAGES,
40
+ idempotent: false,
41
+ invalidates: Object.freeze([])
42
+ })
43
+ });
44
+
45
+ export {
46
+ authRegisterConfirmationResendBodyValidator,
47
+ AUTH_REGISTER_CONFIRMATION_RESEND_MESSAGES,
48
+ authRegisterConfirmationResendCommand
49
+ };
@@ -0,0 +1,12 @@
1
+ export { authRegisterCommand } from "./authRegisterCommand.js";
2
+ export { authRegisterConfirmationResendCommand } from "./authRegisterConfirmationResendCommand.js";
3
+ export { authLoginPasswordCommand } from "./authLoginPasswordCommand.js";
4
+ export { authLoginOtpRequestCommand } from "./authLoginOtpRequestCommand.js";
5
+ export { authLoginOtpVerifyCommand } from "./authLoginOtpVerifyCommand.js";
6
+ export { authLoginOAuthStartCommand } from "./authLoginOAuthStartCommand.js";
7
+ export { authLoginOAuthCompleteCommand } from "./authLoginOAuthCompleteCommand.js";
8
+ export { authPasswordResetRequestCommand } from "./authPasswordResetRequestCommand.js";
9
+ export { authPasswordRecoveryCompleteCommand } from "./authPasswordRecoveryCompleteCommand.js";
10
+ export { authPasswordResetCommand } from "./authPasswordResetCommand.js";
11
+ export { authLogoutCommand } from "./authLogoutCommand.js";
12
+ export { authSessionReadCommand } from "./authSessionReadCommand.js";
@@ -15,6 +15,7 @@ test("authApi exposes the expected methods and request routes", async () => {
15
15
  assert.deepEqual(Object.keys(api), [
16
16
  "session",
17
17
  "register",
18
+ "resendRegisterConfirmation",
18
19
  "login",
19
20
  "requestOtp",
20
21
  "verifyOtp",
@@ -27,14 +28,17 @@ test("authApi exposes the expected methods and request routes", async () => {
27
28
  ]);
28
29
 
29
30
  await api.session();
31
+ await api.resendRegisterConfirmation({ email: "x@example.com" });
30
32
  await api.login({ email: "x@example.com" });
31
33
  await api.logout();
32
34
 
33
35
  assert.equal(calls[0].url, "/api/session");
34
- assert.equal(calls[1].url, "/api/login");
36
+ assert.equal(calls[1].url, "/api/register/confirmation/resend");
35
37
  assert.equal(calls[1].options.method, "POST");
36
- assert.equal(calls[2].url, "/api/logout");
38
+ assert.equal(calls[2].url, "/api/login");
37
39
  assert.equal(calls[2].options.method, "POST");
40
+ assert.equal(calls[3].url, "/api/logout");
41
+ assert.equal(calls[3].options.method, "POST");
38
42
  });
39
43
 
40
44
  test("authApi oauthStartUrl builds provider path with optional returnTo", () => {
@@ -6,6 +6,7 @@ import { AUTH_PATHS, buildAuthOauthStartPath } from "../src/shared/authPaths.js"
6
6
  test("AUTH_PATHS defines canonical auth endpoint paths", () => {
7
7
  assert.equal(Object.isFrozen(AUTH_PATHS), true);
8
8
  assert.equal(AUTH_PATHS.LOGIN, "/api/login");
9
+ assert.equal(AUTH_PATHS.REGISTER_CONFIRMATION_RESEND, "/api/register/confirmation/resend");
9
10
  assert.equal(AUTH_PATHS.LOGOUT, "/api/logout");
10
11
  assert.equal(AUTH_PATHS.SESSION, "/api/session");
11
12
  assert.equal(AUTH_PATHS.OAUTH_START_TEMPLATE, "/api/oauth/:provider/start");
@@ -1,6 +1,7 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { authRegisterCommand } from "../src/shared/commands/authRegisterCommand.js";
4
+ import { authRegisterConfirmationResendCommand } from "../src/shared/commands/authRegisterConfirmationResendCommand.js";
4
5
  import { authLoginPasswordCommand } from "../src/shared/commands/authLoginPasswordCommand.js";
5
6
  import { authLoginOtpRequestCommand } from "../src/shared/commands/authLoginOtpRequestCommand.js";
6
7
  import { authLoginOtpVerifyCommand } from "../src/shared/commands/authLoginOtpVerifyCommand.js";
@@ -15,6 +16,7 @@ import { authSessionReadCommand } from "../src/shared/commands/authSessionReadCo
15
16
  test("auth commands expose canonical operation validator messages", () => {
16
17
  const commands = {
17
18
  authRegisterCommand,
19
+ authRegisterConfirmationResendCommand,
18
20
  authLoginPasswordCommand,
19
21
  authLoginOtpRequestCommand,
20
22
  authLoginOtpVerifyCommand,
@@ -31,3 +33,13 @@ test("auth commands expose canonical operation validator messages", () => {
31
33
  assert.equal(typeof command.operation?.messages, "object", `${label}.operation.messages must be an object.`);
32
34
  }
33
35
  });
36
+
37
+ test("oauth complete command allows provider-less session-pair callbacks", () => {
38
+ const bodySchema = authLoginOAuthCompleteCommand.operation.bodyValidator.schema;
39
+ const bodyRequired = Array.isArray(bodySchema?.required) ? bodySchema.required : [];
40
+ assert.equal(bodyRequired.includes("provider"), false);
41
+
42
+ const responseSchema = authLoginOAuthCompleteCommand.operation.responseValidator.schema;
43
+ const responseRequired = Array.isArray(responseSchema?.required) ? responseSchema.required : [];
44
+ assert.equal(responseRequired.includes("provider"), false);
45
+ });