@jskit-ai/auth-core 0.1.4

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.
Files changed (55) hide show
  1. package/package.descriptor.mjs +95 -0
  2. package/package.json +51 -0
  3. package/src/client/authApi.js +1 -0
  4. package/src/client/index.js +2 -0
  5. package/src/client/providers/AccessCoreClientProvider.js +23 -0
  6. package/src/client/providers/FastifyAuthPolicyClientProvider.js +13 -0
  7. package/src/client/signOutFlow.js +1 -0
  8. package/src/server/inviteTokens.js +41 -0
  9. package/src/server/lib/actionContextContributor.js +36 -0
  10. package/src/server/lib/authPolicySupport.js +38 -0
  11. package/src/server/lib/errors.js +20 -0
  12. package/src/server/lib/index.js +3 -0
  13. package/src/server/lib/objectUtils.js +5 -0
  14. package/src/server/lib/plugin.js +247 -0
  15. package/src/server/lib/routeMeta.js +64 -0
  16. package/src/server/lib/routeVisibilityResolver.js +25 -0
  17. package/src/server/lib/tokens.js +3 -0
  18. package/src/server/membershipAccess.js +67 -0
  19. package/src/server/providers/AccessCoreServiceProvider.js +35 -0
  20. package/src/server/providers/FastifyAuthPolicyServiceProvider.js +124 -0
  21. package/src/server/utils.js +26 -0
  22. package/src/server/validators.js +183 -0
  23. package/src/shared/authApi.js +50 -0
  24. package/src/shared/authConstraints.js +13 -0
  25. package/src/shared/authMethods.js +170 -0
  26. package/src/shared/authPaths.js +24 -0
  27. package/src/shared/commands/authCommandValidators.js +255 -0
  28. package/src/shared/commands/authLoginOAuthCompleteCommand.js +68 -0
  29. package/src/shared/commands/authLoginOAuthStartCommand.js +72 -0
  30. package/src/shared/commands/authLoginOtpRequestCommand.js +56 -0
  31. package/src/shared/commands/authLoginOtpVerifyCommand.js +64 -0
  32. package/src/shared/commands/authLoginPasswordCommand.js +57 -0
  33. package/src/shared/commands/authLogoutCommand.js +23 -0
  34. package/src/shared/commands/authPasswordRecoveryCompleteCommand.js +67 -0
  35. package/src/shared/commands/authPasswordResetCommand.js +49 -0
  36. package/src/shared/commands/authPasswordResetRequestCommand.js +50 -0
  37. package/src/shared/commands/authRegisterCommand.js +57 -0
  38. package/src/shared/commands/authSessionReadCommand.js +26 -0
  39. package/src/shared/index.js +3 -0
  40. package/src/shared/inputNormalization.js +1 -0
  41. package/src/shared/inviteTokens.js +38 -0
  42. package/src/shared/oauthCallbackParams.js +5 -0
  43. package/src/shared/oauthProviders.js +66 -0
  44. package/src/shared/signOutFlow.js +28 -0
  45. package/test/actionContextContributor.test.js +44 -0
  46. package/test/authApi.test.js +47 -0
  47. package/test/authMethods.test.js +95 -0
  48. package/test/authPaths.test.js +17 -0
  49. package/test/commandValidators.test.js +33 -0
  50. package/test/plugin.test.js +250 -0
  51. package/test/providerRuntime.test.js +114 -0
  52. package/test/routeMeta.test.js +95 -0
  53. package/test/routeVisibilityResolver.test.js +34 -0
  54. package/test/serverUtils.test.js +28 -0
  55. package/test/signOutFlow.test.js +67 -0
@@ -0,0 +1,49 @@
1
+ import { Type } from "typebox";
2
+ import { normalizeObjectInput } from "../inputNormalization.js";
3
+ import {
4
+ authPasswordValidator,
5
+ createCommandMessages,
6
+ okMessageResponseValidator
7
+ } from "./authCommandValidators.js";
8
+
9
+ const AUTH_PASSWORD_RESET_MESSAGES = createCommandMessages({
10
+ fields: {
11
+ password: {
12
+ required: "Password is required.",
13
+ minLength: "Password must be at least 8 characters.",
14
+ default: "Password must be at least 8 characters."
15
+ }
16
+ }
17
+ });
18
+
19
+ const authPasswordResetBodyValidator = Object.freeze({
20
+ schema: Type.Object(
21
+ {
22
+ password: authPasswordValidator.schema
23
+ },
24
+ {
25
+ additionalProperties: false
26
+ }
27
+ ),
28
+ normalize: normalizeObjectInput,
29
+ messages: AUTH_PASSWORD_RESET_MESSAGES
30
+ });
31
+
32
+ const authPasswordResetCommand = Object.freeze({
33
+ command: "auth.password.reset",
34
+ operation: Object.freeze({
35
+ method: "POST",
36
+ bodyValidator: authPasswordResetBodyValidator,
37
+ responseValidator: okMessageResponseValidator,
38
+ messages: AUTH_PASSWORD_RESET_MESSAGES,
39
+ idempotent: false,
40
+ invalidates: Object.freeze(["auth.session.read"])
41
+ })
42
+ });
43
+
44
+ export {
45
+ authPasswordResetBodyValidator,
46
+ okMessageResponseValidator,
47
+ AUTH_PASSWORD_RESET_MESSAGES,
48
+ authPasswordResetCommand
49
+ };
@@ -0,0 +1,50 @@
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_PASSWORD_RESET_REQUEST_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 authPasswordResetRequestBodyValidator = Object.freeze({
21
+ schema: Type.Object(
22
+ {
23
+ email: authEmailValidator.schema
24
+ },
25
+ {
26
+ additionalProperties: false
27
+ }
28
+ ),
29
+ normalize: normalizeObjectInput,
30
+ messages: AUTH_PASSWORD_RESET_REQUEST_MESSAGES
31
+ });
32
+
33
+ const authPasswordResetRequestCommand = Object.freeze({
34
+ command: "auth.password.reset.request",
35
+ operation: Object.freeze({
36
+ method: "POST",
37
+ bodyValidator: authPasswordResetRequestBodyValidator,
38
+ responseValidator: okMessageResponseValidator,
39
+ messages: AUTH_PASSWORD_RESET_REQUEST_MESSAGES,
40
+ idempotent: false,
41
+ invalidates: Object.freeze([])
42
+ })
43
+ });
44
+
45
+ export {
46
+ authPasswordResetRequestBodyValidator,
47
+ okMessageResponseValidator,
48
+ AUTH_PASSWORD_RESET_REQUEST_MESSAGES,
49
+ authPasswordResetRequestCommand
50
+ };
@@ -0,0 +1,57 @@
1
+ import { Type } from "typebox";
2
+ import { normalizeObjectInput } from "../inputNormalization.js";
3
+ import {
4
+ authEmailValidator,
5
+ authPasswordValidator,
6
+ createCommandMessages,
7
+ registerResponseValidator
8
+ } from "./authCommandValidators.js";
9
+
10
+ const AUTH_REGISTER_MESSAGES = createCommandMessages({
11
+ fields: {
12
+ email: {
13
+ required: "Email is required.",
14
+ minLength: "Email is required.",
15
+ pattern: "Enter a valid email address.",
16
+ default: "Enter a valid email address."
17
+ },
18
+ password: {
19
+ required: "Password is required.",
20
+ minLength: "Password must be at least 8 characters.",
21
+ default: "Password must be at least 8 characters."
22
+ }
23
+ }
24
+ });
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,
37
+ messages: AUTH_REGISTER_MESSAGES
38
+ });
39
+
40
+ const authRegisterCommand = Object.freeze({
41
+ command: "auth.register",
42
+ operation: Object.freeze({
43
+ method: "POST",
44
+ bodyValidator: authRegisterBodyValidator,
45
+ responseValidator: registerResponseValidator,
46
+ messages: AUTH_REGISTER_MESSAGES,
47
+ idempotent: false,
48
+ invalidates: Object.freeze(["auth.session.read"])
49
+ })
50
+ });
51
+
52
+ export {
53
+ authRegisterBodyValidator,
54
+ registerResponseValidator,
55
+ AUTH_REGISTER_MESSAGES,
56
+ authRegisterCommand
57
+ };
@@ -0,0 +1,26 @@
1
+ import {
2
+ createCommandMessages,
3
+ sessionResponseValidator,
4
+ sessionUnavailableResponseValidator
5
+ } from "./authCommandValidators.js";
6
+
7
+ const AUTH_SESSION_READ_MESSAGES = createCommandMessages();
8
+
9
+ const authSessionReadCommand = Object.freeze({
10
+ command: "auth.session.read",
11
+ operation: Object.freeze({
12
+ method: "GET",
13
+ responseValidator: sessionResponseValidator,
14
+ unavailableResponseValidator: sessionUnavailableResponseValidator,
15
+ messages: AUTH_SESSION_READ_MESSAGES,
16
+ idempotent: true,
17
+ invalidates: Object.freeze([])
18
+ })
19
+ });
20
+
21
+ export {
22
+ sessionResponseValidator,
23
+ sessionUnavailableResponseValidator,
24
+ AUTH_SESSION_READ_MESSAGES,
25
+ authSessionReadCommand
26
+ };
@@ -0,0 +1,3 @@
1
+ export { createApi } from "./authApi.js";
2
+ export { runAuthSignOutFlow } from "./signOutFlow.js";
3
+ export { AUTH_PATHS, buildAuthOauthStartPath } from "./authPaths.js";
@@ -0,0 +1 @@
1
+ export { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
@@ -0,0 +1,38 @@
1
+ const OPAQUE_INVITE_TOKEN_HASH_PREFIX = "inviteh_";
2
+
3
+ function normalizeInviteToken(token) {
4
+ return String(token || "").trim();
5
+ }
6
+
7
+ function isSha256Hex(value) {
8
+ return /^[a-f0-9]{64}$/i.test(String(value || "").trim());
9
+ }
10
+
11
+ function encodeInviteTokenHash(tokenHash) {
12
+ const normalizedTokenHash = String(tokenHash || "")
13
+ .trim()
14
+ .toLowerCase();
15
+ if (!isSha256Hex(normalizedTokenHash)) {
16
+ return "";
17
+ }
18
+
19
+ return `${OPAQUE_INVITE_TOKEN_HASH_PREFIX}${normalizedTokenHash}`;
20
+ }
21
+
22
+ function decodeInviteTokenHash(inviteToken) {
23
+ const normalizedToken = normalizeInviteToken(inviteToken);
24
+ if (!normalizedToken.startsWith(OPAQUE_INVITE_TOKEN_HASH_PREFIX)) {
25
+ return "";
26
+ }
27
+
28
+ const tokenHash = normalizedToken.slice(OPAQUE_INVITE_TOKEN_HASH_PREFIX.length).trim().toLowerCase();
29
+ return isSha256Hex(tokenHash) ? tokenHash : "";
30
+ }
31
+
32
+ export {
33
+ OPAQUE_INVITE_TOKEN_HASH_PREFIX,
34
+ normalizeInviteToken,
35
+ isSha256Hex,
36
+ encodeInviteTokenHash,
37
+ decodeInviteTokenHash
38
+ };
@@ -0,0 +1,5 @@
1
+ const OAUTH_QUERY_PARAM_PROVIDER = "oauthProvider";
2
+ const OAUTH_QUERY_PARAM_INTENT = "oauthIntent";
3
+ const OAUTH_QUERY_PARAM_RETURN_TO = "oauthReturnTo";
4
+
5
+ export { OAUTH_QUERY_PARAM_PROVIDER, OAUTH_QUERY_PARAM_INTENT, OAUTH_QUERY_PARAM_RETURN_TO };
@@ -0,0 +1,66 @@
1
+ const OAUTH_PROVIDER_ID_PATTERN = "^[a-z0-9][a-z0-9_-]{1,31}$";
2
+ const OAUTH_PROVIDER_ID_REGEX = new RegExp(OAUTH_PROVIDER_ID_PATTERN);
3
+
4
+ function normalizeOAuthProviderId(value, { fallback = null } = {}) {
5
+ const normalized = String(value || "")
6
+ .trim()
7
+ .toLowerCase();
8
+
9
+ if (OAUTH_PROVIDER_ID_REGEX.test(normalized)) {
10
+ return normalized;
11
+ }
12
+
13
+ const fallbackNormalized = String(fallback || "")
14
+ .trim()
15
+ .toLowerCase();
16
+ if (OAUTH_PROVIDER_ID_REGEX.test(fallbackNormalized)) {
17
+ return fallbackNormalized;
18
+ }
19
+
20
+ return null;
21
+ }
22
+
23
+ function isValidOAuthProviderId(value) {
24
+ return Boolean(normalizeOAuthProviderId(value, { fallback: null }));
25
+ }
26
+
27
+ function normalizeOAuthProviderList(value, { fallback = [] } = {}) {
28
+ const source = Array.isArray(value)
29
+ ? value
30
+ : typeof value === "string"
31
+ ? value.split(",")
32
+ : value == null
33
+ ? []
34
+ : [value];
35
+
36
+ const normalized = [];
37
+ for (const entry of source) {
38
+ const providerId = normalizeOAuthProviderId(entry, { fallback: null });
39
+ if (!providerId || normalized.includes(providerId)) {
40
+ continue;
41
+ }
42
+ normalized.push(providerId);
43
+ }
44
+
45
+ if (normalized.length > 0) {
46
+ return normalized;
47
+ }
48
+
49
+ if (!Array.isArray(fallback)) {
50
+ return [];
51
+ }
52
+
53
+ if (fallback.length < 1) {
54
+ return [];
55
+ }
56
+
57
+ return normalizeOAuthProviderList(fallback, { fallback: [] });
58
+ }
59
+
60
+ export {
61
+ OAUTH_PROVIDER_ID_PATTERN,
62
+ OAUTH_PROVIDER_ID_REGEX,
63
+ normalizeOAuthProviderId,
64
+ isValidOAuthProviderId,
65
+ normalizeOAuthProviderList
66
+ };
@@ -0,0 +1,28 @@
1
+ function normalizeAuthApi(authApi) {
2
+ if (!authApi || typeof authApi !== "object") {
3
+ throw new TypeError("runAuthSignOutFlow requires authApi.");
4
+ }
5
+ if (typeof authApi.logout !== "function") {
6
+ throw new TypeError("runAuthSignOutFlow requires authApi.logout().");
7
+ }
8
+ return authApi;
9
+ }
10
+
11
+ async function runAuthSignOutFlow({ authApi, clearCsrfTokenCache = null, afterSignOut = null } = {}) {
12
+ const normalizedAuthApi = normalizeAuthApi(authApi);
13
+ const clearFn = typeof clearCsrfTokenCache === "function" ? clearCsrfTokenCache : null;
14
+ const afterFn = typeof afterSignOut === "function" ? afterSignOut : null;
15
+
16
+ try {
17
+ await normalizedAuthApi.logout();
18
+ } finally {
19
+ if (clearFn) {
20
+ clearFn();
21
+ }
22
+ if (afterFn) {
23
+ await afterFn();
24
+ }
25
+ }
26
+ }
27
+
28
+ export { runAuthSignOutFlow };
@@ -0,0 +1,44 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createAuthActionContextContributor } from "../src/server/lib/actionContextContributor.js";
4
+
5
+ test("auth action context contributor skips empty placeholder values", () => {
6
+ const contributor = createAuthActionContextContributor();
7
+
8
+ const contribution = contributor.contribute({
9
+ request: {
10
+ user: null,
11
+ workspace: null,
12
+ membership: null,
13
+ permissions: []
14
+ }
15
+ });
16
+
17
+ assert.deepEqual(contribution, {});
18
+ });
19
+
20
+ test("auth action context contributor contributes real request context values", () => {
21
+ const contributor = createAuthActionContextContributor();
22
+
23
+ const request = {
24
+ user: {
25
+ id: 7
26
+ },
27
+ workspace: {
28
+ id: 11
29
+ },
30
+ membership: {
31
+ roleId: "owner"
32
+ },
33
+ permissions: ["workspace.settings.update", "", " "]
34
+ };
35
+
36
+ const contribution = contributor.contribute({ request });
37
+
38
+ assert.deepEqual(contribution, {
39
+ actor: request.user,
40
+ workspace: request.workspace,
41
+ membership: request.membership,
42
+ permissions: ["workspace.settings.update"]
43
+ });
44
+ });
@@ -0,0 +1,47 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { createApi } from "../src/client/authApi.js";
5
+
6
+ test("authApi exposes the expected methods and request routes", async () => {
7
+ const calls = [];
8
+ const api = createApi({
9
+ request: async (url, options = {}) => {
10
+ calls.push({ url, options });
11
+ return { ok: true };
12
+ }
13
+ });
14
+
15
+ assert.deepEqual(Object.keys(api), [
16
+ "session",
17
+ "register",
18
+ "login",
19
+ "requestOtp",
20
+ "verifyOtp",
21
+ "oauthStartUrl",
22
+ "oauthComplete",
23
+ "requestPasswordReset",
24
+ "completePasswordRecovery",
25
+ "resetPassword",
26
+ "logout"
27
+ ]);
28
+
29
+ await api.session();
30
+ await api.login({ email: "x@example.com" });
31
+ await api.logout();
32
+
33
+ assert.equal(calls[0].url, "/api/session");
34
+ assert.equal(calls[1].url, "/api/login");
35
+ assert.equal(calls[1].options.method, "POST");
36
+ assert.equal(calls[2].url, "/api/logout");
37
+ assert.equal(calls[2].options.method, "POST");
38
+ });
39
+
40
+ test("authApi oauthStartUrl builds provider path with optional returnTo", () => {
41
+ const api = createApi({
42
+ request: async () => ({})
43
+ });
44
+
45
+ assert.equal(api.oauthStartUrl("GitHub"), "/api/oauth/github/start");
46
+ assert.equal(api.oauthStartUrl("github", { returnTo: "/console" }), "/api/oauth/github/start?returnTo=%2Fconsole");
47
+ });
@@ -0,0 +1,95 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ AUTH_METHOD_IDS,
6
+ AUTH_METHOD_KINDS,
7
+ buildAuthMethodDefinitions,
8
+ buildAuthMethodIds,
9
+ buildOAuthMethodDefinitions,
10
+ buildOAuthMethodId,
11
+ findAuthMethodDefinition,
12
+ parseAuthMethodId
13
+ } from "../src/shared/authMethods.js";
14
+ import {
15
+ isValidOAuthProviderId,
16
+ normalizeOAuthProviderId,
17
+ normalizeOAuthProviderList
18
+ } from "../src/shared/oauthProviders.js";
19
+
20
+ test("oauth provider helpers normalize ids and lists", () => {
21
+ assert.equal(normalizeOAuthProviderId(" Google "), "google");
22
+ assert.equal(normalizeOAuthProviderId(""), null);
23
+ assert.equal(normalizeOAuthProviderId("x"), null);
24
+ assert.equal(normalizeOAuthProviderId("", { fallback: "github" }), "github");
25
+
26
+ assert.equal(isValidOAuthProviderId("github"), true);
27
+ assert.equal(isValidOAuthProviderId("bad provider"), false);
28
+
29
+ assert.deepEqual(normalizeOAuthProviderList([" google ", "github", "google", "bad provider"]), [
30
+ "google",
31
+ "github"
32
+ ]);
33
+ assert.deepEqual(normalizeOAuthProviderList("google, github, invalid provider"), ["google", "github"]);
34
+ assert.deepEqual(normalizeOAuthProviderList("", { fallback: ["google"] }), ["google"]);
35
+ });
36
+
37
+ test("auth method builders build provider-aware oauth method definitions", () => {
38
+ const oauthMethods = buildOAuthMethodDefinitions([
39
+ { id: "google", label: "Google" },
40
+ { id: "github", label: "GitHub" },
41
+ { id: "google", label: "Duplicate" },
42
+ { id: "bad provider", label: "Invalid" }
43
+ ]);
44
+
45
+ assert.deepEqual(
46
+ oauthMethods.map((method) => method.id),
47
+ ["oauth:google", "oauth:github"]
48
+ );
49
+
50
+ const methods = buildAuthMethodDefinitions({
51
+ oauthProviders: [
52
+ { id: "google", label: "Google" },
53
+ { id: "github", label: "GitHub" }
54
+ ]
55
+ });
56
+ assert.deepEqual(methods.map((method) => method.id), ["password", "email_otp", "oauth:google", "oauth:github"]);
57
+
58
+ const ids = buildAuthMethodIds({
59
+ oauthProviders: [
60
+ { id: "google", label: "Google" },
61
+ { id: "github", label: "GitHub" }
62
+ ]
63
+ });
64
+ assert.deepEqual(ids, ["password", "email_otp", "oauth:google", "oauth:github"]);
65
+ });
66
+
67
+ test("auth method helpers parse generic oauth ids while definition lookup remains catalog-based", () => {
68
+ assert.deepEqual(parseAuthMethodId("password"), {
69
+ id: "password",
70
+ kind: "password",
71
+ provider: "email"
72
+ });
73
+
74
+ assert.deepEqual(parseAuthMethodId("oauth:github"), {
75
+ id: "oauth:github",
76
+ kind: "oauth",
77
+ provider: "github"
78
+ });
79
+
80
+ assert.equal(parseAuthMethodId("oauth:bad provider"), null);
81
+ assert.equal(parseAuthMethodId("unknown"), null);
82
+
83
+ assert.equal(findAuthMethodDefinition("oauth:github"), null);
84
+ assert.equal(
85
+ findAuthMethodDefinition("oauth:github", {
86
+ oauthProviders: [{ id: "github", label: "GitHub" }]
87
+ })?.label,
88
+ "GitHub"
89
+ );
90
+
91
+ assert.deepEqual(AUTH_METHOD_IDS, ["password", "email_otp"]);
92
+ assert.deepEqual(AUTH_METHOD_KINDS, ["password", "otp", "oauth"]);
93
+ assert.equal(buildOAuthMethodId("google"), "oauth:google");
94
+ assert.equal(buildOAuthMethodId("x"), null);
95
+ });
@@ -0,0 +1,17 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { AUTH_PATHS, buildAuthOauthStartPath } from "../src/shared/authPaths.js";
5
+
6
+ test("AUTH_PATHS defines canonical auth endpoint paths", () => {
7
+ assert.equal(Object.isFrozen(AUTH_PATHS), true);
8
+ assert.equal(AUTH_PATHS.LOGIN, "/api/login");
9
+ assert.equal(AUTH_PATHS.LOGOUT, "/api/logout");
10
+ assert.equal(AUTH_PATHS.SESSION, "/api/session");
11
+ assert.equal(AUTH_PATHS.OAUTH_START_TEMPLATE, "/api/oauth/:provider/start");
12
+ });
13
+
14
+ test("buildAuthOauthStartPath normalizes and encodes provider id", () => {
15
+ assert.equal(buildAuthOauthStartPath("GitHub"), "/api/oauth/github/start");
16
+ assert.equal(buildAuthOauthStartPath("Git Hub"), "/api/oauth/git%20hub/start");
17
+ });
@@ -0,0 +1,33 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { authRegisterCommand } from "../src/shared/commands/authRegisterCommand.js";
4
+ import { authLoginPasswordCommand } from "../src/shared/commands/authLoginPasswordCommand.js";
5
+ import { authLoginOtpRequestCommand } from "../src/shared/commands/authLoginOtpRequestCommand.js";
6
+ import { authLoginOtpVerifyCommand } from "../src/shared/commands/authLoginOtpVerifyCommand.js";
7
+ import { authLoginOAuthStartCommand } from "../src/shared/commands/authLoginOAuthStartCommand.js";
8
+ import { authLoginOAuthCompleteCommand } from "../src/shared/commands/authLoginOAuthCompleteCommand.js";
9
+ import { authPasswordResetRequestCommand } from "../src/shared/commands/authPasswordResetRequestCommand.js";
10
+ import { authPasswordRecoveryCompleteCommand } from "../src/shared/commands/authPasswordRecoveryCompleteCommand.js";
11
+ import { authPasswordResetCommand } from "../src/shared/commands/authPasswordResetCommand.js";
12
+ import { authLogoutCommand } from "../src/shared/commands/authLogoutCommand.js";
13
+ import { authSessionReadCommand } from "../src/shared/commands/authSessionReadCommand.js";
14
+
15
+ test("auth commands expose canonical operation validator messages", () => {
16
+ const commands = {
17
+ authRegisterCommand,
18
+ authLoginPasswordCommand,
19
+ authLoginOtpRequestCommand,
20
+ authLoginOtpVerifyCommand,
21
+ authLoginOAuthStartCommand,
22
+ authLoginOAuthCompleteCommand,
23
+ authPasswordResetRequestCommand,
24
+ authPasswordRecoveryCompleteCommand,
25
+ authPasswordResetCommand,
26
+ authLogoutCommand,
27
+ authSessionReadCommand
28
+ };
29
+
30
+ for (const [label, command] of Object.entries(commands)) {
31
+ assert.equal(typeof command.operation?.messages, "object", `${label}.operation.messages must be an object.`);
32
+ }
33
+ });