@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 +15 -3
- package/dist/bulkMigrate.js +37 -17
- package/dist/generateAppConfig.js +5 -2
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +151 -73
- package/dist/index.mjs +151 -73
- package/dist/initConfig.js +12 -0
- package/dist/setupCoreInstance.js +113 -47
- package/package.json +1 -1
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,
|
|
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
|
package/dist/bulkMigrate.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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