Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auth-core tests + fix missing verifier from reset password flow #778

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 4 additions & 2 deletions integration-tests/stable/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ describe("auth", () => {
>(true);
});

const clientToken = e.select(e.ext.auth.global.client_token);
tc.assert<tc.IsExact<$infer<typeof clientToken>, string | null>>(true);
test("check client token query", () => {
const clientToken = e.select(e.ext.auth.global.client_token);
tc.assert<tc.IsExact<$infer<typeof clientToken>, string | null>>(true);
});
});
4 changes: 4 additions & 0 deletions packages/auth-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
26 changes: 18 additions & 8 deletions packages/auth-core/src/core.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import jwtDecode from "jwt-decode";
import * as edgedb from "edgedb";
import { ResolvedConnectConfig } from "edgedb/dist/conUtils";

Check failure on line 3 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

All imports in the declaration are only used as types. Use `import type`

Check failure on line 3 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

All imports in the declaration are only used as types. Use `import type`

Check failure on line 3 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

All imports in the declaration are only used as types. Use `import type`

Check failure on line 3 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

All imports in the declaration are only used as types. Use `import type`

Check failure on line 3 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

All imports in the declaration are only used as types. Use `import type`

Check failure on line 3 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

All imports in the declaration are only used as types. Use `import type`

import * as pkce from "./pkce";
import { BuiltinOAuthProviderNames, emailPasswordProviderName } from "./consts";

Check failure on line 6 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Import "BuiltinOAuthProviderNames" is only used as types

Check failure on line 6 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Import "BuiltinOAuthProviderNames" is only used as types

Check failure on line 6 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Import "BuiltinOAuthProviderNames" is only used as types

Check failure on line 6 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Import "BuiltinOAuthProviderNames" is only used as types

Check failure on line 6 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Import "BuiltinOAuthProviderNames" is only used as types

Check failure on line 6 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Import "BuiltinOAuthProviderNames" is only used as types

export interface TokenData {
auth_token: string;
Expand All @@ -25,7 +25,7 @@

static async create(client: edgedb.Client) {
const connectConfig: ResolvedConnectConfig = (
await (client as any).pool._getNormalizedConnectConfig()

Check warning on line 28 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Unexpected any. Specify a different type

Check warning on line 28 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Unexpected any. Specify a different type

Check warning on line 28 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 28 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Unexpected any. Specify a different type

Check warning on line 28 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 28 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Unexpected any. Specify a different type
).connectionParams;

const [host, port] = connectConfig.address;
Expand All @@ -37,7 +37,7 @@
}

/** @internal */
public async _get<T extends any = unknown>(path: string): Promise<T> {

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Unexpected any. Specify a different type

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Unexpected any. Specify a different type

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Unexpected any. Specify a different type

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 40 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Unexpected any. Specify a different type
const res = await fetch(new URL(path, this.baseUrl), {
method: "get",
});
Expand All @@ -47,13 +47,13 @@
if (res.headers.get("content-type")?.startsWith("application/json")) {
return res.json();
}
return null as any;

Check warning on line 50 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Unexpected any. Specify a different type

Check warning on line 50 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Unexpected any. Specify a different type

Check warning on line 50 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 50 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Unexpected any. Specify a different type

Check warning on line 50 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 50 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Unexpected any. Specify a different type
}

/** @internal */
public async _post<T extends any = unknown>(

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Unexpected any. Specify a different type

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Unexpected any. Specify a different type

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Unexpected any. Specify a different type

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Constraining the generic type `T` to `any` does nothing and is unnecessary

Check warning on line 54 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Unexpected any. Specify a different type
path: string,
body?: any

Check warning on line 56 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Unexpected any. Specify a different type

Check warning on line 56 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Unexpected any. Specify a different type

Check warning on line 56 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 56 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Unexpected any. Specify a different type

Check warning on line 56 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 56 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Unexpected any. Specify a different type
): Promise<T> {
const res = await fetch(new URL(path, this.baseUrl), {
method: "post",
Expand All @@ -70,7 +70,7 @@
if (res.headers.get("content-type")?.startsWith("application/json")) {
return res.json();
}
return null as any;

Check warning on line 73 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Unexpected any. Specify a different type

Check warning on line 73 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Unexpected any. Specify a different type

Check warning on line 73 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 73 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Unexpected any. Specify a different type

Check warning on line 73 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Unexpected any. Specify a different type

Check warning on line 73 in packages/auth-core/src/core.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Unexpected any. Specify a different type
}

createPKCESession() {
Expand Down Expand Up @@ -141,11 +141,16 @@
}

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) {
Expand All @@ -165,19 +170,24 @@
}
}

async resetPasswordWithResetToken(resetToken: string, password: string) {
return this._post<TokenData>("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
Expand Down
189 changes: 176 additions & 13 deletions packages/auth-core/test/core.test.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -19,34 +24,86 @@ beforeAll(async () => {
configure current database set
ext::auth::AuthConfig::token_time_to_live := <duration>'24 hours';

configure current database set
ext::auth::AuthConfig::allowed_redirect_urls := {'http://example.edgedb.com'};

configure current database set
ext::auth::SMTPConfig::sender := '[email protected]';
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
await new Promise((resolve) => setTimeout(resolve, 1000));
} 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(
"[email protected]",
"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(
"[email protected]",
"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(
"[email protected]"
);

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("[email protected]", "wrongpassword")
Expand All @@ -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(
"[email protected]",
"http://example.edgedb.com/reset_password"
);
expect(sentResetEmail.email_sent).toBe("[email protected]");
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;
},
{}
);
}
Loading
Loading