@jskit-ai/auth-core 0.1.9 → 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.
- package/package.descriptor.mjs +2 -2
- package/package.json +4 -2
- package/src/server/lib/actionContextContributor.js +2 -6
- package/src/server/membershipAccess.js +0 -5
- package/src/server/validators.js +46 -38
- package/src/shared/authApi.js +3 -0
- package/src/shared/authPaths.js +1 -0
- package/src/shared/commands/authCommandValidators.js +1 -1
- package/src/shared/commands/authLoginOAuthCompleteCommand.js +1 -2
- package/src/shared/commands/authRegisterConfirmationResendCommand.js +49 -0
- package/src/shared/commands/index.js +12 -0
- package/test/authApi.test.js +6 -2
- package/test/authPaths.test.js +1 -0
- package/test/commandValidators.test.js +12 -0
package/package.descriptor.mjs
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
};
|
package/src/server/validators.js
CHANGED
|
@@ -93,69 +93,77 @@ function resetPassword(rawPassword) {
|
|
|
93
93
|
return registerPassword(rawPassword);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
function
|
|
97
|
-
const emailCheck = validateEmail(payload.email);
|
|
98
|
-
const passwordCheck = registerPassword(payload.password);
|
|
96
|
+
function buildFieldErrors(entries = []) {
|
|
99
97
|
const fieldErrors = {};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
109
|
+
function buildEmailPasswordInput(payload = {}, { passwordValidator } = {}) {
|
|
116
110
|
const emailCheck = validateEmail(payload.email);
|
|
117
|
-
const
|
|
118
|
-
const
|
|
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
|
|
package/src/shared/authApi.js
CHANGED
|
@@ -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
|
},
|
package/src/shared/authPaths.js
CHANGED
|
@@ -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";
|
package/test/authApi.test.js
CHANGED
|
@@ -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/
|
|
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/
|
|
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", () => {
|
package/test/authPaths.test.js
CHANGED
|
@@ -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
|
+
});
|