@jskit-ai/auth-provider-local-core 0.1.4 → 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.
@@ -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",
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.103",
61
- "@jskit-ai/kernel": "0.1.105",
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.4",
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.103",
15
- "@jskit-ai/kernel": "0.1.105",
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({ backend, config, profileProjector = null, passwordStrategy = null }) {
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({ user: result.user, session: result.session, profileProjector })),
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({ profileProjector = null, profileProjectorFactory = null, passwordStrategy = null } = {}) {
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());