@absolutejs/auth 0.41.0 → 0.43.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.
@@ -0,0 +1,2 @@
1
+ import type { Importer } from './types';
2
+ export declare const auth0Importer: Importer;
@@ -0,0 +1,2 @@
1
+ import type { Importer } from './types';
2
+ export declare const clerkImporter: Importer;
@@ -0,0 +1,10 @@
1
+ import type { ImportResult, Importer } from './types';
2
+ export declare const importers: Record<string, Importer>;
3
+ export type ImportOptions = {
4
+ commit: boolean;
5
+ databaseUrl: string;
6
+ };
7
+ export declare const runImport: (result: ImportResult, options: ImportOptions) => Promise<{
8
+ identityCount: number;
9
+ userCount: number;
10
+ }>;
@@ -0,0 +1,2 @@
1
+ import type { Importer } from './types';
2
+ export declare const luciaImporter: Importer;
@@ -0,0 +1,2 @@
1
+ import type { Importer } from './types';
2
+ export declare const nextauthImporter: Importer;
@@ -0,0 +1,2 @@
1
+ import type { Importer } from './types';
2
+ export declare const supabaseImporter: Importer;
@@ -0,0 +1,26 @@
1
+ export type ImportedUser = {
2
+ createdAtMs: number;
3
+ email: string;
4
+ emailVerified: boolean;
5
+ externalId: string;
6
+ familyName?: string;
7
+ givenName?: string;
8
+ passwordHash?: string;
9
+ passwordHashAlgo?: 'argon2id' | 'bcrypt' | 'scrypt' | 'pbkdf2';
10
+ };
11
+ export type ImportedIdentity = {
12
+ authProvider: string;
13
+ createdAtMs: number;
14
+ metadata?: Record<string, unknown>;
15
+ providerSubject: string;
16
+ userExternalId: string;
17
+ };
18
+ export type ImportResult = {
19
+ identities: ImportedIdentity[];
20
+ source: string;
21
+ users: ImportedUser[];
22
+ };
23
+ export type Importer = {
24
+ parse: (path: string) => Promise<ImportResult>;
25
+ source: string;
26
+ };
@@ -1,6 +1,270 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
3
 
4
+ // src/cli/import/index.ts
5
+ import { neon } from "@neondatabase/serverless";
6
+
7
+ // src/cli/import/auth0.ts
8
+ import { readFile } from "fs/promises";
9
+ var parseUsersFromText = (text) => {
10
+ const trimmed = text.trim();
11
+ if (trimmed.startsWith("[")) {
12
+ return JSON.parse(trimmed);
13
+ }
14
+ return trimmed.split(`
15
+ `).filter((line) => line.length > 0).map((line) => JSON.parse(line));
16
+ };
17
+ var auth0Importer = {
18
+ source: "auth0",
19
+ parse: async (path) => {
20
+ const text = await readFile(path, "utf-8");
21
+ const auth0Users = parseUsersFromText(text);
22
+ const users = auth0Users.map((raw) => ({
23
+ createdAtMs: raw.created_at === undefined ? Date.now() : Date.parse(raw.created_at),
24
+ email: raw.email,
25
+ emailVerified: raw.email_verified ?? false,
26
+ externalId: raw.user_id,
27
+ familyName: raw.family_name,
28
+ givenName: raw.given_name,
29
+ passwordHash: raw.password_hash,
30
+ passwordHashAlgo: raw.password_hash?.startsWith("$2b$") || raw.password_hash?.startsWith("$2a$") || raw.password_hash?.startsWith("$2y$") ? "bcrypt" : undefined
31
+ }));
32
+ const identities = auth0Users.flatMap((raw) => (raw.identities ?? []).filter((identity) => identity.provider !== "auth0").map((identity) => ({
33
+ authProvider: identity.provider.replace("-oauth2", ""),
34
+ createdAtMs: raw.created_at === undefined ? Date.now() : Date.parse(raw.created_at),
35
+ metadata: identity.connection === undefined ? undefined : { connection: identity.connection },
36
+ providerSubject: identity.user_id,
37
+ userExternalId: raw.user_id
38
+ })));
39
+ return { identities, source: "auth0", users };
40
+ }
41
+ };
42
+
43
+ // src/cli/import/clerk.ts
44
+ import { readFile as readFile2 } from "fs/promises";
45
+ var stripOauthPrefix = (provider) => provider.startsWith("oauth_") ? provider.slice("oauth_".length) : provider;
46
+ var clerkImporter = {
47
+ source: "clerk",
48
+ parse: async (path) => {
49
+ const text = await readFile2(path, "utf-8");
50
+ const clerkUsers = JSON.parse(text);
51
+ const users = clerkUsers.filter((raw) => raw.email_addresses !== undefined && raw.email_addresses.length > 0).map((raw) => {
52
+ const primary = raw.email_addresses?.find((e) => raw.primary_email_address_id === undefined || e.email_address.length > 0) ?? raw.email_addresses?.[0];
53
+ return {
54
+ createdAtMs: raw.created_at ?? Date.now(),
55
+ email: primary?.email_address ?? "",
56
+ emailVerified: primary?.verification?.status === "verified",
57
+ externalId: raw.id,
58
+ familyName: raw.last_name,
59
+ givenName: raw.first_name,
60
+ passwordHash: raw.password_digest,
61
+ passwordHashAlgo: raw.password_digest?.startsWith("$argon2id") ? "argon2id" : raw.password_digest?.startsWith("$2") ? "bcrypt" : undefined
62
+ };
63
+ });
64
+ const identities = clerkUsers.flatMap((raw) => (raw.external_accounts ?? []).map((account) => ({
65
+ authProvider: stripOauthPrefix(account.provider),
66
+ createdAtMs: raw.created_at ?? Date.now(),
67
+ providerSubject: account.provider_user_id ?? account.external_id ?? "",
68
+ userExternalId: raw.id
69
+ })));
70
+ return { identities, source: "clerk", users };
71
+ }
72
+ };
73
+
74
+ // src/cli/import/lucia.ts
75
+ import { readFile as readFile3 } from "fs/promises";
76
+ var toMs = (value) => {
77
+ if (value === undefined)
78
+ return Date.now();
79
+ if (typeof value === "number")
80
+ return value < 1000000000000 ? value * 1000 : value;
81
+ return Date.parse(value);
82
+ };
83
+ var luciaImporter = {
84
+ source: "lucia",
85
+ parse: async (path) => {
86
+ const text = await readFile3(path, "utf-8");
87
+ const parsed = JSON.parse(text);
88
+ const passwordByUserId = new Map;
89
+ const oauthKeys = [];
90
+ for (const key of parsed.keys ?? []) {
91
+ const separator = key.id.indexOf(":");
92
+ if (separator < 0)
93
+ continue;
94
+ const provider = key.id.slice(0, separator);
95
+ if (provider === "email" || provider === "username") {
96
+ passwordByUserId.set(key.user_id, key.hashed_password);
97
+ } else {
98
+ oauthKeys.push(key);
99
+ }
100
+ }
101
+ const users = parsed.users.map((raw) => ({
102
+ createdAtMs: toMs(raw.created_at),
103
+ email: raw.email ?? "",
104
+ emailVerified: true,
105
+ externalId: raw.id,
106
+ passwordHash: passwordByUserId.get(raw.id),
107
+ passwordHashAlgo: passwordByUserId.get(raw.id)?.startsWith("$argon2id") ? "argon2id" : passwordByUserId.get(raw.id)?.startsWith("$2") ? "bcrypt" : undefined
108
+ }));
109
+ const identities = oauthKeys.map((key) => {
110
+ const separator = key.id.indexOf(":");
111
+ return {
112
+ authProvider: key.id.slice(0, separator),
113
+ createdAtMs: Date.now(),
114
+ providerSubject: key.id.slice(separator + 1),
115
+ userExternalId: key.user_id
116
+ };
117
+ });
118
+ return { identities, source: "lucia", users };
119
+ }
120
+ };
121
+
122
+ // src/cli/import/nextauth.ts
123
+ import { readFile as readFile4 } from "fs/promises";
124
+ var splitName = (full) => {
125
+ if (full === undefined)
126
+ return { familyName: undefined, givenName: undefined };
127
+ const parts = full.split(/\s+/);
128
+ if (parts.length === 1)
129
+ return { familyName: undefined, givenName: parts[0] };
130
+ return {
131
+ familyName: parts.slice(1).join(" "),
132
+ givenName: parts[0]
133
+ };
134
+ };
135
+ var nextauthImporter = {
136
+ source: "nextauth",
137
+ parse: async (path) => {
138
+ const text = await readFile4(path, "utf-8");
139
+ const parsed = JSON.parse(text);
140
+ const users = parsed.users.map((raw) => {
141
+ const split = splitName(raw.name);
142
+ const passwordHash = parsed.passwordsByUserId?.[raw.id];
143
+ return {
144
+ createdAtMs: Date.now(),
145
+ email: raw.email,
146
+ emailVerified: raw.emailVerified !== null && raw.emailVerified !== undefined,
147
+ externalId: raw.id,
148
+ familyName: split.familyName,
149
+ givenName: split.givenName,
150
+ passwordHash,
151
+ passwordHashAlgo: passwordHash?.startsWith("$argon2id") ? "argon2id" : passwordHash?.startsWith("$2") ? "bcrypt" : undefined
152
+ };
153
+ });
154
+ const identities = (parsed.accounts ?? []).filter((account) => account.type === undefined || account.type === "oauth").map((account) => ({
155
+ authProvider: account.provider,
156
+ createdAtMs: Date.now(),
157
+ providerSubject: account.providerAccountId,
158
+ userExternalId: account.userId
159
+ }));
160
+ return { identities, source: "nextauth", users };
161
+ }
162
+ };
163
+
164
+ // src/cli/import/supabase.ts
165
+ import { readFile as readFile5 } from "fs/promises";
166
+ var splitFullName = (full) => {
167
+ if (full === undefined)
168
+ return { familyName: undefined, givenName: undefined };
169
+ const parts = full.split(/\s+/);
170
+ if (parts.length === 1)
171
+ return { familyName: undefined, givenName: parts[0] };
172
+ return {
173
+ familyName: parts.slice(1).join(" "),
174
+ givenName: parts[0]
175
+ };
176
+ };
177
+ var supabaseImporter = {
178
+ source: "supabase",
179
+ parse: async (path) => {
180
+ const text = await readFile5(path, "utf-8");
181
+ const parsed = JSON.parse(text);
182
+ const users = parsed.users.map((raw) => {
183
+ const meta = raw.raw_user_meta_data ?? {};
184
+ const split = splitFullName(meta.full_name);
185
+ return {
186
+ createdAtMs: raw.created_at === undefined ? Date.now() : Date.parse(raw.created_at),
187
+ email: raw.email,
188
+ emailVerified: raw.email_confirmed_at !== undefined,
189
+ externalId: raw.id,
190
+ familyName: meta.family_name ?? split.familyName,
191
+ givenName: meta.given_name ?? split.givenName,
192
+ passwordHash: raw.encrypted_password,
193
+ passwordHashAlgo: raw.encrypted_password?.startsWith("$2") ? "bcrypt" : undefined
194
+ };
195
+ });
196
+ const identities = (parsed.identities ?? []).map((identity) => ({
197
+ authProvider: identity.provider,
198
+ createdAtMs: identity.created_at === undefined ? Date.now() : Date.parse(identity.created_at),
199
+ providerSubject: identity.provider_id,
200
+ userExternalId: identity.user_id
201
+ }));
202
+ return { identities, source: "supabase", users };
203
+ }
204
+ };
205
+
206
+ // src/cli/import/index.ts
207
+ var importers = {
208
+ auth0: auth0Importer,
209
+ clerk: clerkImporter,
210
+ lucia: luciaImporter,
211
+ nextauth: nextauthImporter,
212
+ supabase: supabaseImporter
213
+ };
214
+ var runImport = async (result, options) => {
215
+ const sql = neon(options.databaseUrl);
216
+ const subByExternalId = new Map;
217
+ for (const user of result.users) {
218
+ subByExternalId.set(user.externalId, crypto.randomUUID());
219
+ }
220
+ if (!options.commit) {
221
+ return {
222
+ identityCount: result.identities.length,
223
+ userCount: result.users.length
224
+ };
225
+ }
226
+ for (const user of result.users) {
227
+ const sub = subByExternalId.get(user.externalId);
228
+ await sql`
229
+ INSERT INTO users (sub, email, password, family_name, given_name, email_verified, created_at)
230
+ VALUES (
231
+ ${sub},
232
+ ${user.email.toLowerCase().trim()},
233
+ ${user.passwordHash ?? null},
234
+ ${user.familyName ?? null},
235
+ ${user.givenName ?? null},
236
+ ${user.emailVerified},
237
+ to_timestamp(${user.createdAtMs} / 1000.0)
238
+ )
239
+ ON CONFLICT (email) DO NOTHING
240
+ `;
241
+ }
242
+ let insertedIdentities = 0;
243
+ for (const identity of result.identities) {
244
+ const sub = subByExternalId.get(identity.userExternalId);
245
+ if (sub === undefined)
246
+ continue;
247
+ const inserted = await sql`
248
+ INSERT INTO auth_identities (id, auth_provider, provider_subject, user_sub, metadata)
249
+ VALUES (
250
+ ${`${identity.authProvider}:${identity.providerSubject}`},
251
+ ${identity.authProvider},
252
+ ${identity.providerSubject},
253
+ ${sub},
254
+ ${identity.metadata ?? {}}
255
+ )
256
+ ON CONFLICT (auth_provider, provider_subject) DO NOTHING
257
+ RETURNING id
258
+ `;
259
+ if (Array.isArray(inserted) && inserted.length > 0)
260
+ insertedIdentities++;
261
+ }
262
+ return {
263
+ identityCount: insertedIdentities,
264
+ userCount: result.users.length
265
+ };
266
+ };
267
+
4
268
  // src/adaptive/postgresStores.ts
5
269
  import { and, desc, eq } from "drizzle-orm";
6
270
  import {
@@ -13,7 +277,7 @@ import {
13
277
  } from "drizzle-orm/pg-core";
14
278
 
15
279
  // src/stores/postgres.ts
16
- import { neon } from "@neondatabase/serverless";
280
+ import { neon as neon2 } from "@neondatabase/serverless";
17
281
  import { drizzle } from "drizzle-orm/neon-http";
18
282
 
19
283
  // src/adaptive/postgresStores.ts
@@ -133,7 +397,7 @@ var warrantsTable = pgTable5("auth_fga_warrants", {
133
397
  });
134
398
 
135
399
  // src/linkedProviders/neonStores.ts
136
- import { neon as neon2 } from "@neondatabase/serverless";
400
+ import { neon as neon3 } from "@neondatabase/serverless";
137
401
  import { desc as desc4, eq as eq6 } from "drizzle-orm";
138
402
  import { drizzle as drizzle2 } from "drizzle-orm/neon-http";
139
403
  import {
@@ -2386,7 +2650,7 @@ var scimTokensTable = pgTable14("auth_scim_tokens", {
2386
2650
  });
2387
2651
 
2388
2652
  // src/session/neonStore.ts
2389
- import { neon as neon3 } from "@neondatabase/serverless";
2653
+ import { neon as neon4 } from "@neondatabase/serverless";
2390
2654
  import { eq as eq15 } from "drizzle-orm";
2391
2655
  import { drizzle as drizzle3 } from "drizzle-orm/neon-http";
2392
2656
  import {
@@ -2703,7 +2967,18 @@ var blockMigrations = {
2703
2967
  };
2704
2968
 
2705
2969
  // src/cli/migrate.ts
2706
- var USAGE = `Usage:
2970
+ var TOP_USAGE = `Usage:
2971
+ bunx absolute-auth <command> [options]
2972
+
2973
+ Commands:
2974
+ migrate Apply the package's Drizzle migrations
2975
+ import <source> <file> Import a user export from another auth library
2976
+ <source> is one of: ${Object.keys(importers).sort().join(", ")}
2977
+ help Print this message
2978
+
2979
+ Run 'bunx absolute-auth <command> --help' for command-specific options.
2980
+ `;
2981
+ var MIGRATE_USAGE = `Usage:
2707
2982
  bunx absolute-auth migrate --db <url> [--blocks block1,block2,...]
2708
2983
 
2709
2984
  Options:
@@ -2713,17 +2988,19 @@ Options:
2713
2988
 
2714
2989
  Available blocks: ${Object.keys(blockMigrations).sort().join(", ")}
2715
2990
  `;
2716
- var consumeFlag = (parsed, flag, args) => {
2717
- if (flag === "--help" || flag === "-h") {
2718
- parsed.help = true;
2719
- } else if (flag === "--db" || flag === "--database-url") {
2720
- parsed.databaseUrl = args.shift();
2721
- } else if (flag === "--blocks") {
2722
- const list = args.shift() ?? "";
2723
- parsed.blocks = list.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => entry);
2724
- }
2725
- };
2726
- var parseArgs = (argv) => {
2991
+ var IMPORT_USAGE = `Usage:
2992
+ bunx absolute-auth import <source> <file> --db <url> [--commit]
2993
+
2994
+ Arguments:
2995
+ source one of: ${Object.keys(importers).sort().join(", ")}
2996
+ file path to the export JSON (see docs/MIGRATE-FROM-*.md per source)
2997
+
2998
+ Options:
2999
+ --db, --database-url Postgres connection string (falls back to DATABASE_URL env)
3000
+ --commit Without this, the run is a dry-run (counts only, no inserts)
3001
+ --help Print this message
3002
+ `;
3003
+ var parseMigrateArgs = (argv) => {
2727
3004
  const parsed = {
2728
3005
  blocks: undefined,
2729
3006
  databaseUrl: undefined,
@@ -2731,34 +3008,67 @@ var parseArgs = (argv) => {
2731
3008
  };
2732
3009
  const args = [...argv];
2733
3010
  while (args.length > 0) {
2734
- consumeFlag(parsed, args.shift(), args);
3011
+ const flag = args.shift();
3012
+ if (flag === "--help" || flag === "-h") {
3013
+ parsed.help = true;
3014
+ } else if (flag === "--db" || flag === "--database-url") {
3015
+ parsed.databaseUrl = args.shift();
3016
+ } else if (flag === "--blocks") {
3017
+ const list = args.shift() ?? "";
3018
+ parsed.blocks = list.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => entry);
3019
+ }
3020
+ }
3021
+ return parsed;
3022
+ };
3023
+ var parseImportArgs = (argv) => {
3024
+ const parsed = {
3025
+ commit: false,
3026
+ databaseUrl: undefined,
3027
+ file: undefined,
3028
+ help: false,
3029
+ source: undefined
3030
+ };
3031
+ const args = [...argv];
3032
+ const positionals = [];
3033
+ while (args.length > 0) {
3034
+ const next = args.shift();
3035
+ if (next === undefined)
3036
+ break;
3037
+ if (next === "--help" || next === "-h") {
3038
+ parsed.help = true;
3039
+ } else if (next === "--db" || next === "--database-url") {
3040
+ parsed.databaseUrl = args.shift();
3041
+ } else if (next === "--commit") {
3042
+ parsed.commit = true;
3043
+ } else if (!next.startsWith("--")) {
3044
+ positionals.push(next);
3045
+ }
2735
3046
  }
3047
+ parsed.source = positionals[0];
3048
+ parsed.file = positionals[1];
2736
3049
  return parsed;
2737
3050
  };
2738
- var die = (message) => {
3051
+ var die = (message, usage) => {
2739
3052
  process.stderr.write(`error: ${message}
2740
3053
 
2741
3054
  `);
2742
- process.stderr.write(USAGE);
3055
+ process.stderr.write(usage);
2743
3056
  process.exit(1);
2744
3057
  };
2745
- var main = async () => {
2746
- const positional = process.argv.slice(2);
2747
- if (positional[0] === "migrate")
2748
- positional.shift();
2749
- const { blocks, databaseUrl, help } = parseArgs(positional);
3058
+ var runMigrate = async (argv) => {
3059
+ const { blocks, databaseUrl, help } = parseMigrateArgs(argv);
2750
3060
  if (help) {
2751
- process.stdout.write(USAGE);
3061
+ process.stdout.write(MIGRATE_USAGE);
2752
3062
  return;
2753
3063
  }
2754
3064
  const resolved = databaseUrl ?? process.env["DATABASE_URL"];
2755
3065
  if (resolved === undefined || resolved.length === 0) {
2756
- die("a Postgres URL is required (--db or DATABASE_URL)");
3066
+ die("a Postgres URL is required (--db or DATABASE_URL)", MIGRATE_USAGE);
2757
3067
  return;
2758
3068
  }
2759
3069
  const unknown = blocks?.filter((block) => !(block in blockMigrations)) ?? [];
2760
3070
  if (unknown.length > 0) {
2761
- die(`unknown block(s): ${unknown.join(", ")}`);
3071
+ die(`unknown block(s): ${unknown.join(", ")}`, MIGRATE_USAGE);
2762
3072
  return;
2763
3073
  }
2764
3074
  const result = await runMigrations({ blocks, databaseUrl: resolved });
@@ -2766,7 +3076,62 @@ var main = async () => {
2766
3076
  ${result.applied.length} migration(s) applied, ${result.skipped.length} skipped.
2767
3077
  `);
2768
3078
  };
3079
+ var runImportCommand = async (argv) => {
3080
+ const { commit, databaseUrl, file, help, source } = parseImportArgs(argv);
3081
+ if (help) {
3082
+ process.stdout.write(IMPORT_USAGE);
3083
+ return;
3084
+ }
3085
+ if (source === undefined) {
3086
+ die("source is required", IMPORT_USAGE);
3087
+ return;
3088
+ }
3089
+ const importer = importers[source];
3090
+ if (importer === undefined) {
3091
+ die(`unknown source "${source}"`, IMPORT_USAGE);
3092
+ return;
3093
+ }
3094
+ if (file === undefined) {
3095
+ die("file path is required", IMPORT_USAGE);
3096
+ return;
3097
+ }
3098
+ const resolved = databaseUrl ?? process.env["DATABASE_URL"];
3099
+ if (resolved === undefined || resolved.length === 0) {
3100
+ die("a Postgres URL is required (--db or DATABASE_URL)", IMPORT_USAGE);
3101
+ return;
3102
+ }
3103
+ process.stdout.write(`[${source}] parsing ${file}\u2026
3104
+ `);
3105
+ const result = await importer.parse(file);
3106
+ process.stdout.write(`[${source}] parsed ${result.users.length} user(s), ${result.identities.length} identity row(s).
3107
+ `);
3108
+ const counts = await runImport(result, { commit, databaseUrl: resolved });
3109
+ if (commit) {
3110
+ process.stdout.write(`[${source}] inserted ${counts.userCount} user(s), ${counts.identityCount} new identity row(s). \u2713
3111
+ `);
3112
+ } else {
3113
+ process.stdout.write(`[${source}] dry-run \u2014 pass --commit to insert.
3114
+ `);
3115
+ }
3116
+ };
3117
+ var main = async () => {
3118
+ const argv = process.argv.slice(2);
3119
+ const command = argv.shift();
3120
+ if (command === undefined || command === "help" || command === "--help") {
3121
+ process.stdout.write(TOP_USAGE);
3122
+ return;
3123
+ }
3124
+ if (command === "migrate") {
3125
+ await runMigrate(argv);
3126
+ return;
3127
+ }
3128
+ if (command === "import") {
3129
+ await runImportCommand(argv);
3130
+ return;
3131
+ }
3132
+ die(`unknown command "${command}"`, TOP_USAGE);
3133
+ };
2769
3134
  await main();
2770
3135
 
2771
- //# debugId=05E38D74A4CE167A64756E2164756E21
3136
+ //# debugId=6E4C57DBFBD5A3C964756E2164756E21
2772
3137
  //# sourceMappingURL=migrate.js.map