diff --git a/README.md b/README.md index 3a44d26..cca0ce8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ Example output ``` % WORKOS_SECRET_KEY=sk_abc123 npx github:workos/migrate-auth0-users \ --user-export dev-123abc.json \ - --password-export password-export.json + --password-export password-export.json \ + --mfa-export mfa-export.json Need to install the following packages: github:workos/migrate-auth0-users Ok to proceed? (y) y diff --git a/example-mfa.json b/example-mfa.json new file mode 100644 index 0000000..eb8a184 --- /dev/null +++ b/example-mfa.json @@ -0,0 +1,2 @@ +{"user_id":"auth0|64db9dba7f18450dc250234","tenant":"wardell-staging","type":"otp","otp_secret":"PA6FWZ3EGU2DMZCSKU2DOQCNNY2X26L2"} +{"user_id":"auth0|64db9dba7f18450dc250234","tenant":"wardell-staging","type":"recovery-code","recovery_code":"$2b$10$zLlN.2NvxP8Vi9PLbstbxOpYpDGteOsmkcslxheOxRfUaknVQTL6G"} \ No newline at end of file diff --git a/example-password.json b/example-password.json new file mode 100644 index 0000000..4508fd7 --- /dev/null +++ b/example-password.json @@ -0,0 +1,2 @@ +{ "_id": "64db9dba7f18450dc250234", "firstName": "Mickey", "lastName": "Mouse", "email":"jason+test8@foo-corp.com", "emailVerified": true, "passwordHash":"$2b$10$.qHPp/srqo1NAAAAAvlkmOdqAbH2Rg0qPv2Txj3ZwXfjJnewSjc4m" } +{ "_id": "64db9dba7f18450dc250234", "firstName": "Donald", "lastName": "Duck", "email":"jason+test9@foo-corp.com", "emailVerified": false, "passwordHash":"$2b$10$.qHPp/srqo1NAAAAAvlkmOdqAbH2Rg0qPv2Txj3ZwXfjJnewSjc4m" } diff --git a/package-lock.json b/package-lock.json index 73127c4..7f03a5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "migrate-auth0-users", "dependencies": { - "@workos-inc/node": "^7.4.0", + "@workos-inc/node": "^7.59.0", "better-sqlite3": "^9.0.0", "dotenv": "^16.3.1", "kysely": "^0.26.3", @@ -359,6 +359,54 @@ "node": ">=12" } }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.15.tgz", + "integrity": "sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", + "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2", + "webcrypto-core": "^1.8.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/better-sqlite3": { "version": "7.6.6", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.6.tgz", @@ -368,15 +416,163 @@ "@types/node": "*" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-disposition": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz", + "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==", + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.4.tgz", + "integrity": "sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==", + "license": "MIT" + }, + "node_modules/@types/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-E/DPgzifH4sM1UMadJMWd6mO2jOd4g1Ejwzx8/uRCDpJis1IrlyQEcGAYEomtAqRYmD5ORbNXMeI9U0RiVGZbg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "license": "MIT" + }, + "node_modules/@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "license": "MIT", + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", + "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.8.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz", "integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==", - "dev": true, "dependencies": { "undici-types": "~5.25.1" } }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.28", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.28.tgz", @@ -393,14 +589,18 @@ "dev": true }, "node_modules/@workos-inc/node": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@workos-inc/node/-/node-7.4.0.tgz", - "integrity": "sha512-dmFU9tMebODaOAMb2Lb9Vq96rcAnaa8TH7VCQf+fDxUBH30u31h7tQPkxeWLHsprFh5sKNUFj6KqHW+9dH/Jow==", + "version": "7.59.0", + "resolved": "https://registry.npmjs.org/@workos-inc/node/-/node-7.59.0.tgz", + "integrity": "sha512-6jXU1jnjO2qm18u8RtywKl1JwQwGACe11o4edVOdnfTstAU1nYz0av7TvCAwGnzS8BSeAQmJgqi8HT93Mz88Cg==", + "license": "MIT", "dependencies": { + "iron-session": "~6.3.1", + "jose": "~5.6.3", + "leb": "^1.0.0", "pluralize": "8.0.0" }, "engines": { - "node": ">=19" + "node": ">=16" } }, "node_modules/ansi-regex": { @@ -425,6 +625,20 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/asn1js": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz", + "integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -534,6 +748,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -721,6 +944,82 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/iron-session": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/iron-session/-/iron-session-6.3.1.tgz", + "integrity": "sha512-3UJ7y2vk/WomAtEySmPgM6qtYF1cZ3tXuWX5GsVX4PJXAcs5y/sV9HuSfpjKS6HkTL/OhZcTDWJNLZ7w+Erx3A==", + "license": "MIT", + "dependencies": { + "@peculiar/webcrypto": "^1.4.0", + "@types/cookie": "^0.5.1", + "@types/express": "^4.17.13", + "@types/koa": "^2.13.5", + "@types/node": "^17.0.41", + "cookie": "^0.5.0", + "iron-webcrypto": "^0.2.5" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "express": ">=4", + "koa": ">=2", + "next": ">=10" + }, + "peerDependenciesMeta": { + "express": { + "optional": true + }, + "koa": { + "optional": true + }, + "next": { + "optional": true + } + } + }, + "node_modules/iron-session/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/iron-webcrypto": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-0.2.8.tgz", + "integrity": "sha512-YPdCvjFMOBjXaYuDj5tiHst5CEk6Xw84Jo8Y2+jzhMceclAnb3+vNPP/CTtb5fO2ZEuXEaO4N+w62Vfko757KA==", + "license": "MIT", + "dependencies": { + "buffer": "^6" + }, + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/iron-webcrypto/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -729,6 +1028,15 @@ "node": ">=8" } }, + "node_modules/jose": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz", + "integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/kysely": { "version": "0.26.3", "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.26.3.tgz", @@ -737,6 +1045,12 @@ "node": ">=14.0.0" } }, + "node_modules/leb": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/leb/-/leb-1.0.0.tgz", + "integrity": "sha512-Y3c3QZfvKWHX60BVOQPhLCvVGmDYWyJEiINE3drOog6KCyN2AOwvuQQzlS3uJg1J85kzpILXIUwRXULWavir+w==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -878,6 +1192,24 @@ "once": "^1.3.1" } }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -1080,6 +1412,12 @@ "node": ">=6" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tsx": { "version": "3.13.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.13.0.tgz", @@ -1123,14 +1461,26 @@ "node_modules/undici-types": { "version": "5.25.3", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", - "dev": true + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/webcrypto-core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", + "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.7.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -1332,6 +1682,44 @@ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "optional": true }, + "@peculiar/asn1-schema": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.15.tgz", + "integrity": "sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==", + "requires": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "requires": { + "tslib": "^2.0.0" + } + }, + "@peculiar/webcrypto": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", + "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", + "requires": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2", + "webcrypto-core": "^1.8.0" + } + }, + "@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "requires": { + "@types/node": "*" + } + }, "@types/better-sqlite3": { "version": "7.6.6", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.6.tgz", @@ -1341,15 +1729,146 @@ "@types/node": "*" } }, + "@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "requires": { + "@types/node": "*" + } + }, + "@types/content-disposition": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz", + "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==" + }, + "@types/cookie": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.4.tgz", + "integrity": "sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==" + }, + "@types/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-E/DPgzifH4sM1UMadJMWd6mO2jOd4g1Ejwzx8/uRCDpJis1IrlyQEcGAYEomtAqRYmD5ORbNXMeI9U0RiVGZbg==", + "requires": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/http-assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==" + }, + "@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==" + }, + "@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==" + }, + "@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "requires": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "@types/koa-compose": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", + "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", + "requires": { + "@types/koa": "*" + } + }, + "@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, "@types/node": { "version": "20.8.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz", "integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==", - "dev": true, "requires": { "undici-types": "~5.25.1" } }, + "@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==" + }, + "@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "requires": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "@types/yargs": { "version": "17.0.28", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.28.tgz", @@ -1366,10 +1885,13 @@ "dev": true }, "@workos-inc/node": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@workos-inc/node/-/node-7.4.0.tgz", - "integrity": "sha512-dmFU9tMebODaOAMb2Lb9Vq96rcAnaa8TH7VCQf+fDxUBH30u31h7tQPkxeWLHsprFh5sKNUFj6KqHW+9dH/Jow==", + "version": "7.59.0", + "resolved": "https://registry.npmjs.org/@workos-inc/node/-/node-7.59.0.tgz", + "integrity": "sha512-6jXU1jnjO2qm18u8RtywKl1JwQwGACe11o4edVOdnfTstAU1nYz0av7TvCAwGnzS8BSeAQmJgqi8HT93Mz88Cg==", "requires": { + "iron-session": "~6.3.1", + "jose": "~5.6.3", + "leb": "^1.0.0", "pluralize": "8.0.0" } }, @@ -1386,6 +1908,16 @@ "color-convert": "^2.0.1" } }, + "asn1js": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz", + "integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==", + "requires": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + } + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1460,6 +1992,11 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, "decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -1589,16 +2126,66 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "iron-session": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/iron-session/-/iron-session-6.3.1.tgz", + "integrity": "sha512-3UJ7y2vk/WomAtEySmPgM6qtYF1cZ3tXuWX5GsVX4PJXAcs5y/sV9HuSfpjKS6HkTL/OhZcTDWJNLZ7w+Erx3A==", + "requires": { + "@peculiar/webcrypto": "^1.4.0", + "@types/cookie": "^0.5.1", + "@types/express": "^4.17.13", + "@types/koa": "^2.13.5", + "@types/node": "^17.0.41", + "cookie": "^0.5.0", + "iron-webcrypto": "^0.2.5" + }, + "dependencies": { + "@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + } + } + }, + "iron-webcrypto": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-0.2.8.tgz", + "integrity": "sha512-YPdCvjFMOBjXaYuDj5tiHst5CEk6Xw84Jo8Y2+jzhMceclAnb3+vNPP/CTtb5fO2ZEuXEaO4N+w62Vfko757KA==", + "requires": { + "buffer": "^6" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } + } + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, + "jose": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz", + "integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==" + }, "kysely": { "version": "0.26.3", "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.26.3.tgz", "integrity": "sha512-yWSgGi9bY13b/W06DD2OCDDHQmq1kwTGYlQ4wpZkMOJqMGCstVCFIvxCCVG4KfY1/3G0MhDAcZsip/Lw8/vJWw==" }, + "leb": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/leb/-/leb-1.0.0.tgz", + "integrity": "sha512-Y3c3QZfvKWHX60BVOQPhLCvVGmDYWyJEiINE3drOog6KCyN2AOwvuQQzlS3uJg1J85kzpILXIUwRXULWavir+w==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1694,6 +2281,19 @@ "once": "^1.3.1" } }, + "pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "requires": { + "tslib": "^2.8.1" + } + }, + "pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==" + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -1821,6 +2421,11 @@ "readable-stream": "^3.1.1" } }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "tsx": { "version": "3.13.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.13.0.tgz", @@ -1849,14 +2454,25 @@ "undici-types": { "version": "5.25.3", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", - "dev": true + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "webcrypto-core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", + "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", + "requires": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.7.0" + } + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index d690baa..0529cd2 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "typescript": "^5.0.0" }, "dependencies": { - "@workos-inc/node": "^7.4.0", + "@workos-inc/node": "^7.59.0", "better-sqlite3": "^9.0.0", "dotenv": "^16.3.1", "kysely": "^0.26.3", diff --git a/src/password-store/database.ts b/src/credentials-store/database.ts similarity index 57% rename from src/password-store/database.ts rename to src/credentials-store/database.ts index 3b6842d..51a9011 100644 --- a/src/password-store/database.ts +++ b/src/credentials-store/database.ts @@ -2,6 +2,7 @@ import { Insertable, Selectable } from "kysely"; export interface Database { passwords: PasswordTable; + otp_secrets: OTPSecretTable; } export interface PasswordTable { @@ -9,5 +10,14 @@ export interface PasswordTable { password_hash: string; } +export interface OTPSecretTable { + auth0_id: string; + otp_secret: string; +} + + export type Password = Selectable; export type NewPassword = Insertable; + +export type Secret = Selectable; +export type NewSecret = Insertable; diff --git a/src/credentials-store/index.ts b/src/credentials-store/index.ts new file mode 100644 index 0000000..d62e7a0 --- /dev/null +++ b/src/credentials-store/index.ts @@ -0,0 +1,95 @@ +import { z } from "zod"; +import { ndjsonStream } from "../ndjson-stream"; +import { Kysely, SqliteDialect } from "kysely"; +import SQLite from "better-sqlite3"; +import { Database, Password, Secret } from "./database"; + +const ExportedPassword = z.object({ + _id: z.string(), + passwordHash: z.string(), +}); + +const ExportedOTPSecret = z.object({ + user_id: z.string(), + type: z.string(), + otp_secret: z.optional(z.string()), +}); + +export class CredentialsStore { + private readonly db: Kysely; + + constructor(public readonly dbPath: string = "migrate-auth0-users.temp.db") { + this.db = new Kysely({ + dialect: new SqliteDialect({ + database: new SQLite(dbPath), + }), + }); + } + + async fromPasswordExport(passwordExportFilePath: string): Promise { + for await (const line of ndjsonStream(passwordExportFilePath)) { + const exportedPassword = ExportedPassword.parse(line); + + await this.db + .insertInto("passwords") + .values({ + auth0_id: `auth0|${exportedPassword._id}`, + password_hash: exportedPassword.passwordHash, + }) + .onConflict((oc) => oc.doNothing()) + .execute(); + } + } + + async fromSecretExport(secretExportFilePath: string): Promise { + for await (const line of ndjsonStream(secretExportFilePath)) { + const exportedSecret = ExportedOTPSecret.parse(line); + if (exportedSecret.type !== "otp" || !exportedSecret.otp_secret) continue; + await this.db + .insertInto("otp_secrets") + .values({ + auth0_id: exportedSecret.user_id, + otp_secret: exportedSecret.otp_secret, + }) + .onConflict((oc) => oc.doNothing()) + .execute(); + } + } + + findPassword(auth0Id: string): Promise { + return this.db + .selectFrom("passwords") + .selectAll() + .where("auth0_id", "=", auth0Id) + .executeTakeFirst(); + } + + findOTPSecret(auth0Id: string): Promise { + return this.db + .selectFrom("otp_secrets") + .selectAll() + .where("auth0_id", "=", auth0Id) + .executeTakeFirst(); + } + + destroy() { + this.db.destroy(); + } + + async prepareSchema() { + await this.db.schema + .createTable("passwords") + .addColumn("auth0_id", "text", (col) => col.primaryKey()) + .addColumn("password_hash", "text", (col) => col.notNull()) + .ifNotExists() + .execute(); + await this.db.schema + .createTable("otp_secrets") + .addColumn("auth0_id", "text", (col) => col.primaryKey()) + .addColumn("otp_secret", "text", (col) => col.notNull()) + .ifNotExists() + .execute(); + } +} + +export type { Password, Secret } from "./database"; diff --git a/src/index.ts b/src/index.ts index 30129bc..33e8012 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import fs from "fs/promises"; import Queue from "p-queue"; import { ndjsonStream } from "./ndjson-stream"; -import { PasswordStore } from "./password-store"; +import { CredentialsStore } from "./credentials-store"; import { Auth0ExportedUser } from "./auth0-exported-user"; import { sleep } from "./sleep"; @@ -22,12 +22,13 @@ const workos = new WorkOS( apiHostname: "localhost", port: 7000, } - : {}, + : {} ); async function findOrCreateUser( exportedUser: Auth0ExportedUser, passwordHash: string | undefined, + otpSecret: string | undefined ) { try { const passwordOptions = passwordHash @@ -37,13 +38,22 @@ async function findOrCreateUser( } : {}; - return await workos.userManagement.createUser({ + const workosUser = await workos.userManagement.createUser({ email: exportedUser.Email, emailVerified: exportedUser["Email Verified"], firstName: exportedUser["Given Name"], lastName: exportedUser["Family Name"], ...passwordOptions, }); + + if (otpSecret) { + await workos.userManagement.enrollAuthFactor({ + type: "totp", + userId: workosUser.id, + totpSecret: otpSecret, + }); + } + return workosUser; } catch (error) { if (error instanceof RateLimitExceededException) { throw error; @@ -53,7 +63,15 @@ async function findOrCreateUser( email: exportedUser.Email.toLowerCase(), }); if (matchingUsers.data.length === 1) { - return matchingUsers.data[0]; + const workosUser = matchingUsers.data[0]; + if (otpSecret) { + await workos.userManagement.enrollAuthFactor({ + type: "totp", + userId: workosUser.id, + totpSecret: otpSecret, + }); + } + return workosUser; } } } @@ -61,30 +79,38 @@ async function findOrCreateUser( async function processLine( line: unknown, recordNumber: number, - passwordStore: PasswordStore, + credentialsStore: CredentialsStore ): Promise { const exportedUser = Auth0ExportedUser.parse(line); - const password = await passwordStore.find(exportedUser.Id); + const password = await credentialsStore.findPassword(exportedUser.Id); if (!password) { console.log( - `(${recordNumber}) No password found in export for ${exportedUser.Id}`, + `(${recordNumber}) No password found in export for ${exportedUser.Id}` + ); + } + + const optSecret = await credentialsStore.findOTPSecret(exportedUser.Id); + if (!optSecret) { + console.log( + `(${recordNumber}) No MFA Secret found in export for ${exportedUser.Id}` ); } const workOsUser = await findOrCreateUser( exportedUser, password?.password_hash, + optSecret?.otp_secret ); if (!workOsUser) { console.error( - `(${recordNumber}) Could not find or create user ${exportedUser.Id}`, + `(${recordNumber}) Could not find or create user ${exportedUser.Id}` ); return false; } console.log( - `(${recordNumber}) Imported Auth0 user ${exportedUser.Id} as WorkOS user ${workOsUser.id}`, + `(${recordNumber}) Imported Auth0 user ${exportedUser.Id} as WorkOS user ${workOsUser.id}` ); return true; @@ -96,6 +122,7 @@ const MAX_CONCURRENT_USER_IMPORTS = 10; async function main() { const { passwordExport: passwordFilePath, + mfaExport: mfaFilePath, userExport: userFilePath, cleanupTempDb, } = await yargs(hideBin(process.argv)) @@ -107,9 +134,14 @@ async function main() { }) .option("password-export", { type: "string", - required: true, + required: false, description: "Path to the password export received from Auth0 support.", }) + .option("mfa-export", { + type: "string", + required: false, + description: "Path to the mfa export received from Auth0 support.", + }) .option("cleanup-temp-db", { type: "boolean", default: true, @@ -119,11 +151,20 @@ async function main() { .version(false) .parse(); - console.log(`Importing password hashes from ${passwordFilePath}`); + const credentialsStore = await new CredentialsStore(); + await credentialsStore.prepareSchema(); - const passwordStore = await new PasswordStore().fromPasswordExport( - passwordFilePath, - ); + if (passwordFilePath) { + console.log(`Importing password hashes from ${passwordFilePath}`); + + await credentialsStore.fromPasswordExport(passwordFilePath); + } + + if (mfaFilePath) { + console.log(`Importing mfa secrets from ${mfaFilePath}`); + + await credentialsStore.fromSecretExport(mfaFilePath); + } console.log(`Importing users from ${userFilePath}`); @@ -143,7 +184,7 @@ async function main() { const successful = await processLine( line, recordNumber, - passwordStore, + credentialsStore ); if (successful) { completedCount++; @@ -156,7 +197,7 @@ async function main() { const retryAfter = (error.retryAfter ?? DEFAULT_RETRY_AFTER) + 1; console.warn( - `Rate limit exceeded. Pausing queue for ${retryAfter} seconds.`, + `Rate limit exceeded. Pausing queue for ${retryAfter} seconds.` ); queue.pause(); @@ -174,13 +215,13 @@ async function main() { await queue.onIdle(); console.log( - `Done importing. ${completedCount} of ${recordCount} user records imported.`, + `Done importing. ${completedCount} of ${recordCount} user records imported.` ); } finally { - passwordStore.destroy(); + credentialsStore.destroy(); if (cleanupTempDb) { - await fs.rm(passwordStore.dbPath); + await fs.rm(credentialsStore.dbPath); } } } diff --git a/src/password-store/index.ts b/src/password-store/index.ts deleted file mode 100644 index 5b9120e..0000000 --- a/src/password-store/index.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { z } from "zod"; -import { ndjsonStream } from "../ndjson-stream"; -import { Kysely, SqliteDialect } from "kysely"; -import SQLite from "better-sqlite3"; -import { Database, Password } from "./database"; - -const ExportedPassword = z.object({ - _id: z.object({ - $oid: z.string(), - }), - passwordHash: z.string(), -}); - -export class PasswordStore { - private readonly db: Kysely; - - constructor(public readonly dbPath: string = "migrate-auth0-users.temp.db") { - this.db = new Kysely({ - dialect: new SqliteDialect({ - database: new SQLite(dbPath), - }), - }); - } - - async fromPasswordExport( - passwordExportFilePath: string, - ): Promise { - await this.prepareSchema(); - - for await (const line of ndjsonStream(passwordExportFilePath)) { - const exportedPassword = ExportedPassword.parse(line); - - await this.db - .insertInto("passwords") - .values({ - auth0_id: exportedPassword._id.$oid, - password_hash: exportedPassword.passwordHash, - }) - .onConflict((oc) => oc.doNothing()) - .execute(); - } - - return this; - } - - find(auth0Id: string): Promise { - return this.db - .selectFrom("passwords") - .selectAll() - .where("auth0_id", "=", auth0Id) - .executeTakeFirst(); - } - - destroy() { - this.db.destroy(); - } - - private async prepareSchema() { - await this.db.schema - .createTable("passwords") - .addColumn("auth0_id", "text", (col) => col.primaryKey()) - .addColumn("password_hash", "text", (col) => col.notNull()) - .ifNotExists() - .execute(); - } -} - -export type { Password } from "./database";