@jskit-ai/auth-provider-local-core 0.1.5 → 0.1.6
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
"packageVersion": 1,
|
|
3
3
|
"packageId": "@jskit-ai/auth-provider-local-core",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6",
|
|
5
5
|
"kind": "runtime",
|
|
6
6
|
"description": "Local auth provider with a file backend default and no database requirement.",
|
|
7
7
|
"dependsOn": [
|
|
@@ -57,8 +57,8 @@ export default Object.freeze({
|
|
|
57
57
|
"mutations": {
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"runtime": {
|
|
60
|
-
"@jskit-ai/auth-core": "0.1.
|
|
61
|
-
"@jskit-ai/kernel": "0.1.
|
|
60
|
+
"@jskit-ai/auth-core": "0.1.105",
|
|
61
|
+
"@jskit-ai/kernel": "0.1.107",
|
|
62
62
|
"nodemailer": "^7.0.10",
|
|
63
63
|
"dotenv": "^16.4.5"
|
|
64
64
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/auth-provider-local-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"./server/lib/index": "./src/server/lib/index.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@jskit-ai/auth-core": "0.1.
|
|
15
|
-
"@jskit-ai/kernel": "0.1.
|
|
14
|
+
"@jskit-ai/auth-core": "0.1.105",
|
|
15
|
+
"@jskit-ai/kernel": "0.1.107",
|
|
16
16
|
"nodemailer": "^7.0.10"
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -92,10 +92,10 @@ function buildActor(user, profile = null) {
|
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
async function buildAuthPayload({ user, session, profileProjector }) {
|
|
95
|
+
async function buildAuthPayload({ user, session, profileProjector, profileOptions = {} }) {
|
|
96
96
|
let profile = buildProfile(user);
|
|
97
97
|
if (profileProjector && typeof profileProjector.syncIdentityProfile === "function") {
|
|
98
|
-
profile = await profileProjector.syncIdentityProfile(profile);
|
|
98
|
+
profile = await profileProjector.syncIdentityProfile(profile, profileOptions);
|
|
99
99
|
if (!profile || typeof profile !== "object") {
|
|
100
100
|
throw new Error("auth.profile.projector.syncIdentityProfile() must return a profile object.");
|
|
101
101
|
}
|
|
@@ -166,6 +166,20 @@ function validateEmailInput(email) {
|
|
|
166
166
|
return normalized;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
function normalizeInvitationInput(value = null) {
|
|
170
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const token = String(value.token || value.inviteToken || "").trim();
|
|
174
|
+
if (!token) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
token,
|
|
179
|
+
source: String(value.source || "workspace-invite").trim() || "workspace-invite"
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
169
183
|
async function maybeSendRecoveryEmail(config, recoveryUrl, email) {
|
|
170
184
|
if (!config.smtpConfigured) {
|
|
171
185
|
return;
|
|
@@ -191,7 +205,13 @@ async function maybeSendRecoveryEmail(config, recoveryUrl, email) {
|
|
|
191
205
|
});
|
|
192
206
|
}
|
|
193
207
|
|
|
194
|
-
function createLocalAuthService({
|
|
208
|
+
function createLocalAuthService({
|
|
209
|
+
backend,
|
|
210
|
+
config,
|
|
211
|
+
profileProjector = null,
|
|
212
|
+
passwordStrategy = null,
|
|
213
|
+
invitationContextResolver = null
|
|
214
|
+
}) {
|
|
195
215
|
if (!backend || typeof backend.withTransaction !== "function") {
|
|
196
216
|
throw new Error("Local auth requires auth.local.backend with withTransaction().");
|
|
197
217
|
}
|
|
@@ -281,10 +301,31 @@ function createLocalAuthService({ backend, config, profileProjector = null, pass
|
|
|
281
301
|
});
|
|
282
302
|
}
|
|
283
303
|
|
|
304
|
+
async function resolveProfileOptionsForRegistration(input = {}, { email } = {}) {
|
|
305
|
+
const invitation = normalizeInvitationInput(input.invitation);
|
|
306
|
+
if (!invitation) {
|
|
307
|
+
return {};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let resolvedInvitation = invitation;
|
|
311
|
+
if (invitationContextResolver && typeof invitationContextResolver.resolveInvitationContext === "function") {
|
|
312
|
+
resolvedInvitation = await invitationContextResolver.resolveInvitationContext({
|
|
313
|
+
...invitation,
|
|
314
|
+
email
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
source: resolvedInvitation?.source || "workspace-invite",
|
|
320
|
+
invitation: resolvedInvitation
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
284
324
|
async function register(input = {}) {
|
|
285
325
|
const email = validateEmailInput(input.email);
|
|
286
326
|
validatePasswordInput(input.password);
|
|
287
327
|
const displayName = normalizeDisplayName(input.displayName, email);
|
|
328
|
+
const profileOptions = await resolveProfileOptionsForRegistration(input, { email });
|
|
288
329
|
const password = await passwords.hashPassword(input.password);
|
|
289
330
|
const result = await backend.withTransaction(async (tx) => {
|
|
290
331
|
const existing = await tx.users.findByEmail(email);
|
|
@@ -303,7 +344,12 @@ function createLocalAuthService({ backend, config, profileProjector = null, pass
|
|
|
303
344
|
};
|
|
304
345
|
});
|
|
305
346
|
return {
|
|
306
|
-
...(await buildAuthPayload({
|
|
347
|
+
...(await buildAuthPayload({
|
|
348
|
+
user: result.user,
|
|
349
|
+
session: result.session,
|
|
350
|
+
profileProjector,
|
|
351
|
+
profileOptions
|
|
352
|
+
})),
|
|
307
353
|
requiresEmailConfirmation: false
|
|
308
354
|
};
|
|
309
355
|
}
|
|
@@ -174,12 +174,26 @@ class AuthLocalServiceProvider {
|
|
|
174
174
|
const backend = scope.make("auth.local.backend");
|
|
175
175
|
const profileProjector = scope.has("auth.profile.projector")
|
|
176
176
|
? {
|
|
177
|
-
async syncIdentityProfile(profile) {
|
|
177
|
+
async syncIdentityProfile(profile, options = {}) {
|
|
178
178
|
const projector = scope.make("auth.profile.projector");
|
|
179
179
|
if (!projector || typeof projector.syncIdentityProfile !== "function") {
|
|
180
180
|
throw new Error("auth.profile.projector.syncIdentityProfile() must be a function.");
|
|
181
181
|
}
|
|
182
|
-
return projector.syncIdentityProfile(profile);
|
|
182
|
+
return projector.syncIdentityProfile(profile, options);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
: null;
|
|
186
|
+
const invitationContextResolver = scope.has("auth.invitationContextResolver")
|
|
187
|
+
? {
|
|
188
|
+
async resolveInvitationContext(invitation, options = {}) {
|
|
189
|
+
const resolver = scope.make("auth.invitationContextResolver");
|
|
190
|
+
if (typeof resolver === "function") {
|
|
191
|
+
return resolver(invitation, options);
|
|
192
|
+
}
|
|
193
|
+
if (!resolver || typeof resolver.resolveInvitationContext !== "function") {
|
|
194
|
+
throw new Error("auth.invitationContextResolver.resolveInvitationContext() must be a function.");
|
|
195
|
+
}
|
|
196
|
+
return resolver.resolveInvitationContext(invitation, options);
|
|
183
197
|
}
|
|
184
198
|
}
|
|
185
199
|
: null;
|
|
@@ -190,7 +204,8 @@ class AuthLocalServiceProvider {
|
|
|
190
204
|
backend,
|
|
191
205
|
config,
|
|
192
206
|
profileProjector,
|
|
193
|
-
passwordStrategy
|
|
207
|
+
passwordStrategy,
|
|
208
|
+
invitationContextResolver
|
|
194
209
|
});
|
|
195
210
|
});
|
|
196
211
|
}
|
|
@@ -50,7 +50,12 @@ function createReplyFixture() {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
async function createStartedApp({
|
|
53
|
+
async function createStartedApp({
|
|
54
|
+
profileProjector = null,
|
|
55
|
+
profileProjectorFactory = null,
|
|
56
|
+
passwordStrategy = null,
|
|
57
|
+
invitationContextResolver = null
|
|
58
|
+
} = {}) {
|
|
54
59
|
const storeDir = await fs.mkdtemp(path.join(os.tmpdir(), "jskit-auth-local-"));
|
|
55
60
|
const app = createApplication();
|
|
56
61
|
app.instance("appConfig", createAppConfigFixture());
|
|
@@ -79,6 +84,9 @@ async function createStartedApp({ profileProjector = null, profileProjectorFacto
|
|
|
79
84
|
if (passwordStrategy) {
|
|
80
85
|
app.instance("auth.local.passwordStrategy", passwordStrategy);
|
|
81
86
|
}
|
|
87
|
+
if (invitationContextResolver) {
|
|
88
|
+
app.instance("auth.invitationContextResolver", invitationContextResolver);
|
|
89
|
+
}
|
|
82
90
|
await app.start({
|
|
83
91
|
providers: [
|
|
84
92
|
ActionRuntimeServiceProvider,
|
|
@@ -586,6 +594,71 @@ test("local auth provider projects app profile when auth.profile.projector is in
|
|
|
586
594
|
assert.equal(registered.actor.profileSource, "users");
|
|
587
595
|
});
|
|
588
596
|
|
|
597
|
+
test("local auth provider passes resolved invitation context into profile projection during registration", async () => {
|
|
598
|
+
const resolverCalls = [];
|
|
599
|
+
const projectionCalls = [];
|
|
600
|
+
const { app } = await createStartedApp({
|
|
601
|
+
invitationContextResolver: {
|
|
602
|
+
async resolveInvitationContext(invitation) {
|
|
603
|
+
resolverCalls.push(invitation);
|
|
604
|
+
return {
|
|
605
|
+
token: invitation.token,
|
|
606
|
+
workspaceId: "workspace-7",
|
|
607
|
+
workspaceSlug: "acme",
|
|
608
|
+
workspaceName: "Acme",
|
|
609
|
+
email: invitation.email,
|
|
610
|
+
roleSid: "member",
|
|
611
|
+
expiresAt: "2030-01-01T00:00:00.000Z"
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
profileProjector: {
|
|
616
|
+
async syncIdentityProfile(profile, options) {
|
|
617
|
+
projectionCalls.push({ profile, options });
|
|
618
|
+
return {
|
|
619
|
+
...profile,
|
|
620
|
+
id: "invited-app-user",
|
|
621
|
+
profileSource: "users"
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
const authService = app.make("authService");
|
|
627
|
+
|
|
628
|
+
const registered = await authService.register({
|
|
629
|
+
email: "Invitee@Example.com",
|
|
630
|
+
password: "invited password",
|
|
631
|
+
displayName: "Invited",
|
|
632
|
+
invitation: {
|
|
633
|
+
token: "invite-token",
|
|
634
|
+
source: "workspace-invite"
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
assert.deepEqual(resolverCalls, [
|
|
639
|
+
{
|
|
640
|
+
token: "invite-token",
|
|
641
|
+
source: "workspace-invite",
|
|
642
|
+
email: "invitee@example.com"
|
|
643
|
+
}
|
|
644
|
+
]);
|
|
645
|
+
assert.equal(projectionCalls.length, 1);
|
|
646
|
+
assert.equal(projectionCalls[0].profile.email, "invitee@example.com");
|
|
647
|
+
assert.deepEqual(projectionCalls[0].options, {
|
|
648
|
+
source: "workspace-invite",
|
|
649
|
+
invitation: {
|
|
650
|
+
token: "invite-token",
|
|
651
|
+
workspaceId: "workspace-7",
|
|
652
|
+
workspaceSlug: "acme",
|
|
653
|
+
workspaceName: "Acme",
|
|
654
|
+
email: "invitee@example.com",
|
|
655
|
+
roleSid: "member",
|
|
656
|
+
expiresAt: "2030-01-01T00:00:00.000Z"
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
assert.equal(registered.actor.appUserId, "invited-app-user");
|
|
660
|
+
});
|
|
661
|
+
|
|
589
662
|
test("local auth provider rejects AUTH_PROVIDER mismatches", async () => {
|
|
590
663
|
const app = createApplication();
|
|
591
664
|
app.instance("appConfig", createAppConfigFixture());
|