@supertokens-plugins/rownd-nodejs 0.5.0 → 0.6.0

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/README.md CHANGED
@@ -116,6 +116,7 @@ import { createMagicLinkWithConfirmationBypass } from "@supertokens-plugins/rown
116
116
 
117
117
  const magicLink = await createMagicLinkWithConfirmationBypass({
118
118
  email: "user@example.com",
119
+ tenantId: "tenant-a",
119
120
  clientDomain: "browser",
120
121
  redirectToPath: "/profile",
121
122
  displayContext: "browser",
@@ -126,7 +127,7 @@ const magicLink = await createMagicLinkWithConfirmationBypass({
126
127
 
127
128
  `clientDomain` must be a configured `clientDomains` key, not a raw domain. Omit it to use the SuperTokens website domain.
128
129
 
129
- Pass exactly one of `email` or `phoneNumber`. The helper returns the rewritten magic link with `bypassDeviceConfirmation=true`.
130
+ Pass exactly one of `email` or `phoneNumber`. `tenantId` defaults to `public`. The helper returns the rewritten magic link with `bypassDeviceConfirmation=true`.
130
131
 
131
132
  Before skipping the cross-device confirmation prompt, the frontend should validate the callback against the plugin. Routes are mounted under your SuperTokens `apiBasePath`, which defaults to `/auth`.
132
133
 
@@ -141,16 +142,25 @@ If validation fails, the frontend should show the normal cross-device confirmati
141
142
 
142
143
  Routes are mounted under your SuperTokens `apiBasePath`, which defaults to `/auth`. The migration endpoint is the main Rownd-to-SuperTokens session handoff endpoint; the plugin also exposes Rownd-compatible app config, guest, user, metadata, field, and sign-out endpoints under `{apiBasePath}/plugin/rownd/...`.
143
144
 
145
+ Unauthenticated migration and guest routes accept an optional `tenantId` query parameter. It defaults to `public`. SuperTokens Core validates the tenant when the operation runs. Authenticated identity-field and sign-out operations use the tenant from the current session; custom Rownd metadata remains global to the SuperTokens user.
146
+
144
147
  > [!IMPORTANT]
145
- > The plugin always migrates users and sessions into the `public` tenant.
146
148
  > Rownd users with multiple supported login methods are rejected unless SuperTokens account linking is enabled in the target environment.
147
149
 
148
150
  ### Migrate
149
151
 
150
152
  - **POST** `{apiBasePath}/plugin/rownd/migrate`
151
153
  - **Default**: `POST /auth/plugin/rownd/migrate`
154
+ - **Non-public tenant**: `POST /auth/plugin/rownd/migrate?tenantId=tenant-a`
152
155
  - **Headers**: `Authorization: Bearer <Rownd_JWT>`. Header-token clients should also send `rid: session`, `fdi-version: 1.18`, and `st-auth-mode: header`.
153
- - **Description**: Validates the Rownd JWT, ensures the user is migrated to SuperTokens in the `public` tenant, syncs Rownd user data to SuperTokens UserMetadata, and then creates a new SuperTokens session for that user. Header-token clients must receive `st-access-token`, `st-refresh-token`, and `front-token` response headers.
156
+ - **Description**: Validates the Rownd JWT, imports new users with their Rownd profile data, ensures the selected login method is associated with the requested SuperTokens tenant, and then creates a new SuperTokens session in that tenant. Header-token clients must receive `st-access-token`, `st-refresh-token`, and `front-token` response headers.
157
+
158
+ ### Guest
159
+
160
+ - **POST** `{apiBasePath}/plugin/rownd/guest`
161
+ - **Default**: `POST /auth/plugin/rownd/guest`
162
+ - **Non-public tenant**: `POST /auth/plugin/rownd/guest?tenantId=tenant-a`
163
+ - **Description**: Creates a guest or instant user and session in the requested tenant.
154
164
 
155
165
  ## Debug Logging
156
166
 
@@ -228,6 +238,8 @@ RowndMigrationPlugin.init({
228
238
 
229
239
  The package includes a bulk migration script for importing Rownd users into SuperTokens.
230
240
 
241
+ Set `supertokens.tenantId` in the generated configuration to associate every imported login method with a non-public tenant. It defaults to `public` when omitted. Resuming from a checkpoint with a different tenant is rejected.
242
+
231
243
  The script runs from a YAML config file generated from the included template.
232
244
 
233
245
  ### Usage
@@ -40,6 +40,7 @@ var fs2 = __toESM(require("fs/promises"));
40
40
  var import_zod2 = require("zod");
41
41
 
42
42
  // src/rownd-compatibility.ts
43
+ var import_node_crypto = require("crypto");
43
44
  var import_supertokens_node = __toESM(require("supertokens-node"));
44
45
  var import_usermetadata = __toESM(require("supertokens-node/recipe/usermetadata"));
45
46
 
@@ -55,10 +56,15 @@ var IDENTITY_USER_DATA_FIELDS = /* @__PURE__ */ new Set([
55
56
  "google_id",
56
57
  "apple_id"
57
58
  ]);
59
+ var SUPERTOKENS_FAKE_EMAIL_DOMAIN = "stfakeemail.supertokens.com";
60
+ function buildSuperTokensFakeEmail(thirdPartyUserId, thirdPartyId) {
61
+ const hash = (0, import_node_crypto.createHash)("sha256").update(`${thirdPartyId}:${thirdPartyUserId}`).digest("hex").slice(0, 32);
62
+ return `st-${thirdPartyId}-${hash}@${SUPERTOKENS_FAKE_EMAIL_DOMAIN}`;
63
+ }
58
64
  function isIdentityField(field) {
59
65
  return IDENTITY_USER_DATA_FIELDS.has(field);
60
66
  }
61
- function mapRowndUserToSuperTokens(rowndUser) {
67
+ function mapRowndUserToSuperTokens(rowndUser, tenantId) {
62
68
  const loginMethods = [];
63
69
  const rowndUserData = rowndUser.data || {};
64
70
  const rowndUserVerifiedData = rowndUser.verified_data || {};
@@ -66,41 +72,41 @@ function mapRowndUserToSuperTokens(rowndUser) {
66
72
  throw new Error("Rownd user has no user_id");
67
73
  }
68
74
  if (rowndUserData.google_id) {
69
- if (!rowndUserData.email) {
70
- throw new Error("Rownd Google user is missing email");
71
- }
75
+ const googleEmail = rowndUserData.email ?? buildSuperTokensFakeEmail(rowndUserData.google_id, "google");
72
76
  loginMethods.push({
73
77
  recipeId: "thirdparty",
74
78
  thirdPartyId: "google",
75
79
  thirdPartyUserId: rowndUserData.google_id,
76
- email: rowndUserData.email,
77
- isVerified: !!rowndUserVerifiedData.google_id
80
+ email: googleEmail,
81
+ isVerified: !!rowndUserData.email && !!rowndUserVerifiedData.google_id,
82
+ ...tenantId ? { tenantIds: [tenantId] } : {}
78
83
  });
79
84
  }
80
85
  if (rowndUserData.apple_id) {
81
- if (!rowndUserData.email) {
82
- throw new Error("Rownd Apple user is missing email");
83
- }
86
+ const appleEmail = rowndUserData.email ?? buildSuperTokensFakeEmail(rowndUserData.apple_id, "apple");
84
87
  loginMethods.push({
85
88
  recipeId: "thirdparty",
86
89
  thirdPartyId: "apple",
87
90
  thirdPartyUserId: rowndUserData.apple_id,
88
- email: rowndUserData.email,
89
- isVerified: !!rowndUserVerifiedData.apple_id
91
+ email: appleEmail,
92
+ isVerified: !!rowndUserData.email && !!rowndUserVerifiedData.apple_id,
93
+ ...tenantId ? { tenantIds: [tenantId] } : {}
90
94
  });
91
95
  }
92
96
  if (rowndUserData.phone_number) {
93
97
  loginMethods.push({
94
98
  recipeId: "passwordless",
95
99
  phoneNumber: rowndUserData.phone_number,
96
- isVerified: !!rowndUserVerifiedData.phone_number
100
+ isVerified: !!rowndUserVerifiedData.phone_number,
101
+ ...tenantId ? { tenantIds: [tenantId] } : {}
97
102
  });
98
103
  }
99
104
  if (rowndUserData.email && !rowndUserData.google_id && !rowndUserData.apple_id) {
100
105
  loginMethods.push({
101
106
  recipeId: "passwordless",
102
107
  email: rowndUserData.email,
103
- isVerified: !!rowndUserVerifiedData.email
108
+ isVerified: !!rowndUserVerifiedData.email,
109
+ ...tenantId ? { tenantIds: [tenantId] } : {}
104
110
  });
105
111
  }
106
112
  let authLevel = rowndUser.auth_level;
@@ -112,7 +118,8 @@ function mapRowndUserToSuperTokens(rowndUser) {
112
118
  thirdPartyId,
113
119
  thirdPartyUserId: rowndUserData.user_id,
114
120
  email: `${rowndUserData.user_id}@anonymous.local`,
115
- isVerified: false
121
+ isVerified: false,
122
+ ...tenantId ? { tenantIds: [tenantId] } : {}
116
123
  });
117
124
  }
118
125
  const userMetadata = buildRowndUserMetadata(rowndUser);
@@ -159,12 +166,15 @@ var ConfigSchema = import_zod.z.object({
159
166
  appKey: import_zod.z.string(),
160
167
  appSecret: import_zod.z.string(),
161
168
  bearerToken: import_zod.z.string().optional(),
162
- pageSize: import_zod.z.number().int().positive()
169
+ pageSize: import_zod.z.number().int().positive(),
170
+ oidcClientSecrets: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
171
+ provisionOidcClientIdAliases: import_zod.z.boolean().default(true)
163
172
  }),
164
173
  supertokens: import_zod.z.object({
165
174
  connectionURI: import_zod.z.string(),
166
175
  apiKey: import_zod.z.string().optional(),
167
- batchSize: import_zod.z.number().int().positive()
176
+ batchSize: import_zod.z.number().int().positive(),
177
+ tenantId: import_zod.z.string().min(1).default("public")
168
178
  }),
169
179
  thirdPartyProviders: import_zod.z.array(
170
180
  import_zod.z.object({
@@ -362,6 +372,7 @@ Options:
362
372
  var CheckpointSchema = import_zod2.z.object({
363
373
  cursor: import_zod2.z.string().optional(),
364
374
  importedCount: import_zod2.z.number().int().nonnegative().optional(),
375
+ tenantId: import_zod2.z.string().optional(),
365
376
  updatedAt: import_zod2.z.string()
366
377
  });
367
378
  async function loadCheckpoint(checkpointFile) {
@@ -452,6 +463,11 @@ async function importUsersBatch(config, users, totalImportedBeforeBatch) {
452
463
  }
453
464
  async function migrateRowndUsersToSuperTokens(config) {
454
465
  const checkpoint = config.checkpoint.resume ? await loadCheckpoint(config.checkpoint.file) : null;
466
+ if (checkpoint && (checkpoint.tenantId ?? "public") !== config.supertokens.tenantId) {
467
+ throw new Error(
468
+ `Checkpoint tenant ${checkpoint.tenantId ?? "public"} does not match configured tenant ${config.supertokens.tenantId}`
469
+ );
470
+ }
455
471
  const failedMappingsFile = getFailedMappingsFilePath(config.checkpoint.file);
456
472
  let cursor = checkpoint?.cursor;
457
473
  let totalProcessed = 0;
@@ -485,7 +501,10 @@ async function migrateRowndUsersToSuperTokens(config) {
485
501
  try {
486
502
  mappedUsers.push({
487
503
  rowndUserId,
488
- user: mapRowndUserToSuperTokens(rowndUser)
504
+ user: mapRowndUserToSuperTokens(
505
+ rowndUser,
506
+ config.supertokens.tenantId === "public" ? void 0 : config.supertokens.tenantId
507
+ )
489
508
  });
490
509
  } catch (error) {
491
510
  const errorMessage = error instanceof Error ? error.message : "Unknown mapping error";
@@ -507,6 +526,7 @@ async function migrateRowndUsersToSuperTokens(config) {
507
526
  await saveCheckpoint(config.checkpoint.file, {
508
527
  cursor,
509
528
  importedCount: totalImported,
529
+ tenantId: config.supertokens.tenantId,
510
530
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
511
531
  });
512
532
  console.log(`Processed ${totalProcessed} users so far`);
@@ -61,12 +61,15 @@ var ConfigSchema = import_zod.z.object({
61
61
  appKey: import_zod.z.string(),
62
62
  appSecret: import_zod.z.string(),
63
63
  bearerToken: import_zod.z.string().optional(),
64
- pageSize: import_zod.z.number().int().positive()
64
+ pageSize: import_zod.z.number().int().positive(),
65
+ oidcClientSecrets: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
66
+ provisionOidcClientIdAliases: import_zod.z.boolean().default(true)
65
67
  }),
66
68
  supertokens: import_zod.z.object({
67
69
  connectionURI: import_zod.z.string(),
68
70
  apiKey: import_zod.z.string().optional(),
69
- batchSize: import_zod.z.number().int().positive()
71
+ batchSize: import_zod.z.number().int().positive(),
72
+ tenantId: import_zod.z.string().min(1).default("public")
70
73
  }),
71
74
  thirdPartyProviders: import_zod.z.array(
72
75
  import_zod.z.object({
package/dist/index.d.mts CHANGED
@@ -746,6 +746,7 @@ type RowndUserMetadata = {
746
746
  field: string;
747
747
  value: string;
748
748
  created_at: string;
749
+ tenantId?: string;
749
750
  }>;
750
751
  [key: string]: unknown;
751
752
  };
package/dist/index.d.ts CHANGED
@@ -746,6 +746,7 @@ type RowndUserMetadata = {
746
746
  field: string;
747
747
  value: string;
748
748
  created_at: string;
749
+ tenantId?: string;
749
750
  }>;
750
751
  [key: string]: unknown;
751
752
  };