diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4654c6680..033c061fe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -122,11 +122,58 @@ jobs: run: | yarn workspace edgedb test + auth-test: + # Skip tests when doing a release to avoid the workflow race + # when the release PR gets merged by the bot. + if: needs.prep.outputs.version == 0 + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.edgedb-version == 'nightly' }} + strategy: + matrix: + node-version: ["20"] + os: [ubuntu-latest] + edgedb-version: ["nightly"] + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 50 + submodules: true + + - name: Set up Node ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Set up Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + - name: Install dev deps + run: | + yarn + + - name: Build + run: | + yarn workspace edgedb build + yarn workspace @edgedb/auth-core build + + - name: Install EdgeDB + uses: edgedb/setup-edgedb@8bc9e10005674ec772652b86e1fdd476e6462284 + with: + instance-name: test + server-version: ${{ matrix.edgedb-version }} + + - name: Run tests + run: | + yarn workspace @edgedb/auth-core test + # This job exists solely to act as the test job aggregate to be # targeted by branch policies. regression-tests: name: "Regression Tests" - needs: [test] + needs: [test, auth-test] runs-on: ubuntu-latest steps: - run: echo OK diff --git a/integration-tests/stable/auth.test.ts b/integration-tests/stable/auth.test.ts index e26ce505d..98c3fa03c 100644 --- a/integration-tests/stable/auth.test.ts +++ b/integration-tests/stable/auth.test.ts @@ -35,6 +35,8 @@ describe("auth", () => { >(true); }); - const clientToken = e.select(e.ext.auth.global.client_token); - tc.assert, string | null>>(true); + test("check client token query", () => { + const clientToken = e.select(e.ext.auth.global.client_token); + tc.assert, string | null>>(true); + }); }); diff --git a/packages/auth-core/package.json b/packages/auth-core/package.json index 128bc2df4..66608beff 100644 --- a/packages/auth-core/package.json +++ b/packages/auth-core/package.json @@ -22,9 +22,13 @@ }, "devDependencies": { "@types/jest": "^29.5.2", + "@types/mailparser": "^3.4.4", "@types/node": "^20.8.4", + "@types/smtp-server": "^3.5.10", "edgedb": "^1.3.6", "jest": "29.5.0", + "mailparser": "^3.6.5", + "smtp-server": "^3.13.0", "ts-jest": "29.1.0", "typescript": "^5.2.2" }, diff --git a/packages/auth-core/src/core.ts b/packages/auth-core/src/core.ts index af28bdf15..e4d2a4895 100644 --- a/packages/auth-core/src/core.ts +++ b/packages/auth-core/src/core.ts @@ -141,11 +141,16 @@ export class Auth { } async sendPasswordResetEmail(email: string, resetUrl: string) { - return this._post<{ email_sent: string }>("send-reset-email", { - provider: emailPasswordProviderName, - email, - reset_url: resetUrl, - }); + const { challenge, verifier } = pkce.createVerifierChallengePair(); + return { + verifier, + ...(await this._post<{ email_sent: string }>("send-reset-email", { + provider: emailPasswordProviderName, + challenge, + email, + reset_url: resetUrl, + })), + }; } static checkPasswordResetTokenValid(resetToken: string) { @@ -165,19 +170,24 @@ export class Auth { } } - async resetPasswordWithResetToken(resetToken: string, password: string) { - return this._post("reset-password", { + async resetPasswordWithResetToken( + resetToken: string, + verifier: string, + password: string + ) { + const { code } = await this._post<{ code: string }>("reset-password", { provider: emailPasswordProviderName, reset_token: resetToken, password, }); + return this.getToken(code, verifier); } async getProvidersInfo() { // TODO: cache this data when we have a way to invalidate on config update try { return await this.client.queryRequiredSingle<{ - oauth: { name: string; display_name: string }[]; + oauth: { name: BuiltinOAuthProviderNames; display_name: string }[]; emailPassword: boolean; }>(` with diff --git a/packages/auth-core/test/core.test.ts b/packages/auth-core/test/core.test.ts index c998ba2b1..02ef0e12e 100644 --- a/packages/auth-core/test/core.test.ts +++ b/packages/auth-core/test/core.test.ts @@ -1,13 +1,18 @@ import crypto from "node:crypto"; -import { getClient } from "./testbase"; +import { getClient, getConnectOptions } from "./testbase"; +import { MockSMTPServer } from "./mock_smtp"; import { Auth } from "../src/core"; const SIGNING_KEY = crypto.randomBytes(32).toString("base64"); +const smtpServer = new MockSMTPServer(); + beforeAll(async () => { const client = getClient(); + const smtpAddress = await smtpServer.listen(); + try { await client.execute(` create extension pgcrypto; @@ -19,11 +24,25 @@ beforeAll(async () => { configure current database set ext::auth::AuthConfig::token_time_to_live := '24 hours'; + configure current database set + ext::auth::AuthConfig::allowed_redirect_urls := {'http://example.edgedb.com'}; + configure current database set ext::auth::SMTPConfig::sender := 'noreply@example.edgedb.com'; + configure current database set + ext::auth::SMTPConfig::validate_certs := false; + configure current database set + ext::auth::SMTPConfig::host := ${JSON.stringify(smtpAddress.address)}; + configure current database set + ext::auth::SMTPConfig::port := ${smtpAddress.port}; configure current database insert ext::auth::EmailPasswordProviderConfig {}; + configure current database + insert ext::auth::GitHubOAuthProvider { + secret := 'secret', + client_id := 'client_id', + }; `); // wait for config to be applied @@ -31,22 +50,60 @@ beforeAll(async () => { } finally { client.close(); } -}, 20_000); +}, 30_000); + +afterAll(async () => { + await smtpServer.close(); + // getClient().execute(` + // delete ext::auth::Factor; + // delete ext::auth::PKCEChallenge; + // delete ext::auth::Identity; + // `); +}); -test("test password signup/signin flow", async () => { +test("test password signup/signin/reset flow", async () => { const client = getClient({ tlsSecurity: "insecure" }); try { const auth = await Auth.create(client); const signupToken = await auth.signupWithEmailPassword( "test@example.edgedb.com", - "supersecretpassword" + "supersecretpassword", + "http://example.edgedb.com/verify" ); - expect(typeof signupToken.auth_token).toBe("string"); - expect(typeof signupToken.identity_id).toBe("string"); - expect(signupToken.provider_refresh_token).toBeNull(); - expect(signupToken.provider_token).toBeNull(); + expect(signupToken.status).toBe("verificationRequired"); + expect(typeof (signupToken as any).verifier).toBe("string"); + + await expect( + auth.signinWithEmailPassword( + "test@example.edgedb.com", + "supersecretpassword" + ) + ).rejects.toThrow(); + + const verifyEmail = await smtpServer.getMail(); + const verificationUrl = new URL(verifyEmail.text!.trim()); + + expect(verificationUrl.origin).toBe("http://example.edgedb.com"); + expect(verificationUrl.pathname).toBe("/verify"); + expect(verificationUrl.searchParams.has("verification_token")).toBeTruthy(); + expect(verificationUrl.searchParams.get("provider")).toBe( + "builtin::local_emailpassword" + ); + expect(verificationUrl.searchParams.get("email")).toBe( + "test@example.edgedb.com" + ); + + const verifyToken = await auth.verifyEmailPasswordSignup( + verificationUrl.searchParams.get("verification_token")!, + (signupToken as any).verifier + ); + + expect(typeof verifyToken.auth_token).toBe("string"); + expect(typeof verifyToken.identity_id).toBe("string"); + expect(verifyToken.provider_refresh_token).toBeNull(); + expect(verifyToken.provider_token).toBeNull(); await expect( auth.signinWithEmailPassword("test@example.edgedb.com", "wrongpassword") @@ -60,13 +117,119 @@ test("test password signup/signin flow", async () => { const identity = (await client.withGlobals({ "ext::auth::client_token": signinToken.auth_token, }).querySingle(` - select assert_single(global ext::auth::ClientTokenIdentity { - * - }) - `)) as any; - + select global ext::auth::ClientTokenIdentity { + * + } + `)) as any; expect(identity.id).toBe(signinToken.identity_id); + + const sentResetEmail = await auth.sendPasswordResetEmail( + "test@example.edgedb.com", + "http://example.edgedb.com/reset_password" + ); + expect(sentResetEmail.email_sent).toBe("test@example.edgedb.com"); + expect(typeof sentResetEmail.verifier).toBe("string"); + + const resetEmail = await smtpServer.getMail(); + const resetUrl = new URL(resetEmail.text!.trim()); + + expect(resetUrl.origin).toBe("http://example.edgedb.com"); + expect(resetUrl.pathname).toBe("/reset_password"); + expect(resetUrl.searchParams.has("reset_token")).toBeTruthy(); + + const resetAuthToken = await auth.resetPasswordWithResetToken( + resetUrl.searchParams.get("reset_token")!, + sentResetEmail.verifier, + "newsecretpassword" + ); + expect(typeof resetAuthToken.auth_token).toBe("string"); + expect(resetAuthToken.identity_id).toBe(identity.id); + } finally { + await client.close(); + } +}, 60_000); + +test("get providers info", async () => { + const client = getClient({ tlsSecurity: "insecure" }); + try { + const auth = await Auth.create(client); + + const providersInfo = await auth.getProvidersInfo(); + + expect(providersInfo).toEqual({ + oauth: [ + { + name: "builtin::oauth_github", + display_name: "GitHub", + }, + ], + emailPassword: true, + }); + + // check oauth provider info can be passed to getOAuthUrl + auth + .createPKCESession() + .getOAuthUrl( + providersInfo.oauth[0].name, + "http://example.edgedb.com/callback" + ); } finally { await client.close(); } }); + +test("pkce session urls", async () => { + const client = getClient({ tlsSecurity: "insecure" }); + try { + const auth = await Auth.create(client); + + const pkceSession = auth.createPKCESession(); + + expect(typeof pkceSession.verifier).toBe("string"); + expect(typeof pkceSession.challenge).toBe("string"); + + const { port, database } = getConnectOptions(); + + const githubOauthUrl = new URL( + pkceSession.getOAuthUrl( + "builtin::oauth_github", + "http://example.edgedb.com/callback", + "http://example.edgedb.com/signup_callback" + ) + ); + expect(githubOauthUrl.origin).toBe(`http://localhost:${port}`); + expect(githubOauthUrl.pathname).toBe(`/db/${database}/ext/auth/authorize`); + expect(searchParamsToMap(githubOauthUrl.searchParams)).toEqual({ + challenge: pkceSession.challenge, + provider: "builtin::oauth_github", + redirect_to: "http://example.edgedb.com/callback", + redirect_to_on_signup: "http://example.edgedb.com/signup_callback", + }); + + const uiSigninUrl = new URL(pkceSession.getHostedUISigninUrl()); + expect(uiSigninUrl.origin).toBe(`http://localhost:${port}`); + expect(uiSigninUrl.pathname).toBe(`/db/${database}/ext/auth/ui/signin`); + expect(searchParamsToMap(uiSigninUrl.searchParams)).toEqual({ + challenge: pkceSession.challenge, + }); + + const uiSignupUrl = new URL(pkceSession.getHostedUISignupUrl()); + expect(uiSignupUrl.origin).toBe(`http://localhost:${port}`); + expect(uiSignupUrl.pathname).toBe(`/db/${database}/ext/auth/ui/signup`); + expect(searchParamsToMap(uiSignupUrl.searchParams)).toEqual({ + challenge: pkceSession.challenge, + }); + } finally { + await client.close(); + } +}); + +function searchParamsToMap(params: URLSearchParams) { + return [...params.entries()].reduce<{ [key: string]: string }>( + (map, [key, val]) => { + map[key] = val; + return map; + }, + {} + ); +} diff --git a/packages/auth-core/test/mock_smtp.ts b/packages/auth-core/test/mock_smtp.ts new file mode 100644 index 000000000..5d6631f16 --- /dev/null +++ b/packages/auth-core/test/mock_smtp.ts @@ -0,0 +1,55 @@ +import { AddressInfo } from "node:net"; +import { SMTPServer } from "smtp-server"; +import { ParsedMail, simpleParser } from "mailparser"; + +export class MockSMTPServer { + private server: SMTPServer; + + private mailQueue: ParsedMail[] = []; + private mailPromiseQueue: ((mail: ParsedMail) => void)[] = []; + + constructor() { + this.server = new SMTPServer({ + secure: false, + authOptional: true, + onData: async (stream, session, callback) => { + const parsedMail = await simpleParser(stream); + callback(); + + if (this.mailPromiseQueue.length) { + this.mailPromiseQueue.pop()!(parsedMail); + } else { + this.mailQueue.unshift(parsedMail); + } + }, + }); + } + + listen() { + return new Promise((resolve, reject) => + this.server.listen(0, () => { + const address = this.server.server.address(); + + if (!address || typeof address === "string") { + return reject(new Error("SMTP server failed to start as expected")); + } + + resolve(address); + }) + ); + } + + close() { + return new Promise((resolve) => this.server.close(resolve)); + } + + async getMail() { + if (this.mailQueue.length) { + return this.mailQueue.pop()!; + } else { + return new Promise((resolve) => { + this.mailPromiseQueue.unshift(resolve); + }); + } + } +} diff --git a/packages/auth-core/test/testbase.ts b/packages/auth-core/test/testbase.ts index 3d6344206..e4cdfe3f5 100644 --- a/packages/auth-core/test/testbase.ts +++ b/packages/auth-core/test/testbase.ts @@ -4,3 +4,6 @@ import * as testbase from "../../driver/test/testbase"; export const getClient = testbase.getClient as unknown as ( opts?: ConnectOptions ) => Client; + +export const getConnectOptions = + testbase.getConnectOptions as unknown as () => ConnectOptions; diff --git a/packages/auth-nextjs/src/app/index.ts b/packages/auth-nextjs/src/app/index.ts index 7cbc41fdc..bd965a8c8 100644 --- a/packages/auth-nextjs/src/app/index.ts +++ b/packages/auth-nextjs/src/app/index.ts @@ -354,10 +354,15 @@ export class NextAppAuth extends NextAuth { ["email"], "email missing from request body" ); - (await this.core).sendPasswordResetEmail( - email, - this.options.passwordResetUrl - ); + const { verifier } = await ( + await this.core + ).sendPasswordResetEmail(email, this.options.passwordResetUrl); + cookies().set({ + name: this.options.pkceVerifierCookieName, + value: verifier, + httpOnly: true, + sameSite: "strict", + }); return new Response(null, { status: 204 }); } case "emailpassword/reset-password": { @@ -368,6 +373,14 @@ export class NextAppAuth extends NextAuth { } let tokenData: TokenData; try { + const verifier = req.cookies.get( + this.options.pkceVerifierCookieName + )?.value; + if (!verifier) { + return onEmailPasswordReset({ + error: new Error("no pkce verifier cookie found"), + }); + } const [resetToken, password] = _extractParams( await _getReqBody(req), ["reset_token", "password"], @@ -376,7 +389,7 @@ export class NextAppAuth extends NextAuth { tokenData = await ( await this.core - ).resetPasswordWithResetToken(resetToken, password); + ).resetPasswordWithResetToken(resetToken, verifier, password); } catch (err) { return onEmailPasswordReset({ error: err instanceof Error ? err : new Error(String(err)), @@ -388,6 +401,7 @@ export class NextAppAuth extends NextAuth { httpOnly: true, sameSite: "strict", }); + cookies().delete(this.options.pkceVerifierCookieName); return onEmailPasswordReset({ error: null, tokenData }); } case "emailpassword/resend-verification-email": { @@ -472,16 +486,28 @@ export class NextAppAuth extends NextAuth { throw new Error(`'passwordResetUrl' option not configured`); } const [email] = _extractParams(data, ["email"], "email missing"); - await ( + const { verifier } = await ( await this.core ).sendPasswordResetEmail( email, `${this.options.baseUrl}/${this.options.passwordResetUrl}` ); + cookies().set({ + name: this.options.pkceVerifierCookieName, + value: verifier, + httpOnly: true, + sameSite: "strict", + }); }, emailPasswordResetPassword: async ( data: FormData | { resetToken: string; password: string } ) => { + const verifier = cookies().get( + this.options.pkceVerifierCookieName + )?.value; + if (!verifier) { + throw new Error("no pkce verifier cookie found"); + } const [resetToken, password] = _extractParams( data, ["reset_token", "password"], @@ -489,13 +515,14 @@ export class NextAppAuth extends NextAuth { ); const tokenData = await ( await this.core - ).resetPasswordWithResetToken(resetToken, password); + ).resetPasswordWithResetToken(resetToken, verifier, password); cookies().set({ name: this.options.authCookieName, value: tokenData.auth_token, httpOnly: true, sameSite: "strict", }); + cookies().delete(this.options.pkceVerifierCookieName); return tokenData; }, emailPasswordResendVerificationEmail: async ( diff --git a/yarn.lock b/yarn.lock index b846aada5..c6a532857 100644 --- a/yarn.lock +++ b/yarn.lock @@ -918,6 +918,14 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@selderee/plugin-htmlparser2@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz#d5b5e29a7ba6d3958a1972c7be16f4b2c188c517" + integrity sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ== + dependencies: + domhandler "^5.0.3" + selderee "^0.11.0" + "@sinclair/typebox@^0.25.16": version "0.25.24" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" @@ -1030,6 +1038,14 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/mailparser@^3.4.4": + version "3.4.4" + resolved "https://registry.yarnpkg.com/@types/mailparser/-/mailparser-3.4.4.tgz#0bd71e205573b9dd9a445e10a8b8cb0e45420998" + integrity sha512-C6Znp2QVS25JqtuPyxj38Qh+QoFcLycdxsvcc6IZCGekhaMBzbdTXzwGzhGoYb3TfKu8IRCNV0sV1o3Od97cEQ== + dependencies: + "@types/node" "*" + iconv-lite "^0.6.3" + "@types/node@*": version "18.16.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.0.tgz#4668bc392bb6938637b47e98b1f2ed5426f33316" @@ -1047,6 +1063,13 @@ dependencies: undici-types "~5.25.1" +"@types/nodemailer@*": + version "6.4.14" + resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.14.tgz#5c81a5e856db7f8ede80013e6dbad7c5fb2283e2" + integrity sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA== + dependencies: + "@types/node" "*" + "@types/prettier@^2.1.5": version "2.7.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" @@ -1057,6 +1080,14 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== +"@types/smtp-server@^3.5.10": + version "3.5.10" + resolved "https://registry.yarnpkg.com/@types/smtp-server/-/smtp-server-3.5.10.tgz#06d0338aea519469529847a12b0903678fdd6bea" + integrity sha512-i3Jx7sJ2qF52vjaOf3HguulXlWRFf6BSfsRLsIdmytDyVGv7KkhSs+gR9BXJnJWg1Ljkh/56Fh1Xqwa6u6X7zw== + dependencies: + "@types/node" "*" + "@types/nodemailer" "*" + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -1334,6 +1365,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base32.js@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base32.js/-/base32.js-0.1.0.tgz#b582dec693c2f11e893cf064ee6ac5b6131a2202" + integrity sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1591,7 +1627,7 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.2.2: +deepmerge@^4.2.2, deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -1630,6 +1666,20 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + domexception@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" @@ -1637,6 +1687,22 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + electron-to-chromium@^1.4.284: version "1.4.371" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.371.tgz#393983ef087268a20c926a89be30e9f0bfc803b0" @@ -1652,7 +1718,12 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -entities@^4.4.0: +encoding-japanese@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encoding-japanese/-/encoding-japanese-2.0.0.tgz#fa0226e5469e7b5b69a04fea7d5481bd1fa56936" + integrity sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ== + +entities@^4.2.0, entities@^4.4.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -2157,6 +2228,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + html-encoding-sniffer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" @@ -2169,6 +2245,27 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-to-text@9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-9.0.5.tgz#6149a0f618ae7a0db8085dca9bbf96d32bb8368d" + integrity sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg== + dependencies: + "@selderee/plugin-htmlparser2" "^0.11.0" + deepmerge "^4.3.1" + dom-serializer "^2.0.0" + htmlparser2 "^8.0.2" + selderee "^0.11.0" + +htmlparser2@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" @@ -2191,7 +2288,7 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -iconv-lite@0.6.3: +iconv-lite@0.6.3, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -2237,6 +2334,11 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ipv6-normalize@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ipv6-normalize/-/ipv6-normalize-1.0.1.tgz#1b3258290d365fa83239e89907dde4592e7620a8" + integrity sha512-Bm6H79i01DjgGTCWjUuCjJ6QDo1HB96PT/xCYuyJUP9WFbVDrLSbG4EZCvOCun2rNswZb0c3e4Jt/ws795esHA== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -2815,6 +2917,11 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +leac@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912" + integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg== + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -2836,11 +2943,48 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +libbase64@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/libbase64/-/libbase64-1.2.1.tgz#fb93bf4cb6d730f29b92155b6408d1bd2176a8c8" + integrity sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew== + +libmime@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/libmime/-/libmime-5.2.0.tgz#c4ed5cbd2d9fdd27534543a68bb8d17c658d51d8" + integrity sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw== + dependencies: + encoding-japanese "2.0.0" + iconv-lite "0.6.3" + libbase64 "1.2.1" + libqp "2.0.1" + +libmime@5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/libmime/-/libmime-5.2.1.tgz#a1075eaf702fa597161948dcae3afd03be383ac4" + integrity sha512-A0z9O4+5q+ZTj7QwNe/Juy1KARNb4WaviO4mYeFC4b8dBT2EEqK2pkM+GC8MVnkOjqhl5nYQxRgnPYRRTNmuSQ== + dependencies: + encoding-japanese "2.0.0" + iconv-lite "0.6.3" + libbase64 "1.2.1" + libqp "2.0.1" + +libqp@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/libqp/-/libqp-2.0.1.tgz#b8fed76cc1ea6c9ceff8888169e4e0de70cd5cf2" + integrity sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec" + integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw== + dependencies: + uc.micro "^1.0.1" + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -2879,6 +3023,30 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +mailparser@^3.6.5: + version "3.6.5" + resolved "https://registry.yarnpkg.com/mailparser/-/mailparser-3.6.5.tgz#c82d312de32a6fa3d67254e044f8c4eb8f533c31" + integrity sha512-nteTpF0Khm5JLOnt4sigmzNdUH/6mO7PZ4KEnvxf4mckyXYFFhrtAWZzbq/V5aQMH+049gA7ZjfLdh+QiX2Uqg== + dependencies: + encoding-japanese "2.0.0" + he "1.2.0" + html-to-text "9.0.5" + iconv-lite "0.6.3" + libmime "5.2.1" + linkify-it "4.0.1" + mailsplit "5.4.0" + nodemailer "6.9.3" + tlds "1.240.0" + +mailsplit@5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/mailsplit/-/mailsplit-5.4.0.tgz#9f4692fadd9013e9ce632147d996931d2abac6ba" + integrity sha512-wnYxX5D5qymGIPYLwnp6h8n1+6P6vz/MJn5AzGjZ8pwICWssL+CCQjWBIToOVHASmATot4ktvlLo6CyLfOXWYA== + dependencies: + libbase64 "1.2.1" + libmime "5.2.0" + libqp "2.0.1" + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -3005,6 +3173,16 @@ node-releases@^2.0.8: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== +nodemailer@6.9.3: + version "6.9.3" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.3.tgz#e4425b85f05d83c43c5cd81bf84ab968f8ef5cbe" + integrity sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg== + +nodemailer@6.9.4: + version "6.9.4" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.4.tgz#93bd4a60eb0be6fa088a0483340551ebabfd2abf" + integrity sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA== + normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -3117,6 +3295,14 @@ parse5@^7.0.0, parse5@^7.1.1: dependencies: entities "^4.4.0" +parseley@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/parseley/-/parseley-0.12.1.tgz#4afd561d50215ebe259e3e7a853e62f600683aef" + integrity sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw== + dependencies: + leac "^0.6.0" + peberminta "^0.9.0" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -3142,6 +3328,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +peberminta@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.9.0.tgz#8ec9bc0eb84b7d368126e71ce9033501dca2a352" + integrity sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -3307,6 +3498,13 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" +selderee@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/selderee/-/selderee-0.11.0.tgz#6af0c7983e073ad3e35787ffe20cefd9daf0ec8a" + integrity sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA== + dependencies: + parseley "^0.12.0" + semver@7.x, semver@^7.3.5, semver@^7.3.7: version "7.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" @@ -3356,6 +3554,15 @@ slash@^4.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== +smtp-server@^3.13.0: + version "3.13.0" + resolved "https://registry.yarnpkg.com/smtp-server/-/smtp-server-3.13.0.tgz#014d67d5065ea067b0120954aaa8a5ce7353c39c" + integrity sha512-thVFqpwrHIJ25rXjXA6RYFUO35el2O+X7WJ006qMVAyFs5Ss6XGPJASg7Fh1QvT28ADIv9hGGXmgR+kaSEikwQ== + dependencies: + base32.js "0.1.0" + ipv6-normalize "1.0.1" + nodemailer "6.9.4" + source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -3497,6 +3704,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +tlds@1.240.0: + version "1.240.0" + resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.240.0.tgz#3d3d776d97aa079e43ef4d2f9ef9845e55cff08e" + integrity sha512-1OYJQenswGZSOdRw7Bql5Qu7uf75b+F3HFBXbqnG/ifHa0fev1XcG+3pJf3pA/KC6RtHQzfKgIf1vkMlMG7mtQ== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -3652,6 +3864,11 @@ typescript@^5.2.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +uc.micro@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + undici-types@~5.25.1: version "5.25.3" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3"