Repository: octokit/oauth-app.js (ts)

Issue:
[BUG]: Using `oauth.allowSignup: false` has no effect while using `createNodeMiddleware`
### What happened?

When I am using `createNodeMiddleware` I noticed that the authorization URL is using `allowSignup` as `true` by default. In order to disable that I added `oauth.allowSignup: false` in the App options as such
```js
function setupGitHub() {
  return new App({
    appId: process.env.GITHUB_APP_ID!,
    privateKey: readFileSync(process.env.GITHUB_KEY!, 'utf8'),
    webhooks: {
      secret: process.env.GITHUB_WEBHOOK_SECRET!
    },
    oauth: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
      allowSignup: false,
      redirectUrl: process.env.GITHUB_REDIRECT_URL
    }
  })
}
```
But this had no effect.

This seems to me because I didnt add the `allowsignup` parameter to the `login` request. The relevant line where the bug is happening is https://github.com/octokit/oauth-app.js/blob/728b032e3c6233350bb76744e5940c28d9cba01e/src/methods/get-web-flow-authorization-url.ts#L24
and the `options.allowSignup` in the above line is true because
https://github.com/octokit/oauth-app.js/blob/728b032e3c6233350bb76744e5940c28d9cba01e/src/middleware/handle-request.ts#L79
and `query.allowSignup !== "false"` evaulates to `true` if the login request doesn't contain the `allowSignup` parameter.

### Versions

Octokit.js@2.0.10 and @octokit/oauth-app@4.1.0

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

Pull request description:
fix: handle undefined values of `allowSignup` when they are not set in the login url or app options
<!-- Please refer to our contributing docs for any questions on submitting a pull request -->


<!-- Issues are required for both bug fixes and features. -->
Resolves #369

----

## Behavior

### Before the change?
<!-- Please describe the current behavior that you are modifying. -->

* Setting the app option of `allowSignup` would still create a URL with `allow_signup=true`
* The only way to set that option was to call the login URL with `allowSignup=false`
* This means the app option was not having any effect

### After the change?
<!-- Please describe the behavior or changes that are being added by this PR. -->

* If the login URL or the app option, doesn't set the `allowSignup` setting, its defaulted to `true` as previous
* If the login URL sets the `allowSignup` but the app options doesn't, the value is taken from the login URL, whether true or false
* If the login URL doesn't set the `allowSignup` but the app options does, the value is taken from the app options, whether true or false
* If both the login URL and the app options sets the value of  `allowSignup`, the final value is an `OR` operation between the 2 values


### Other information
<!-- Any other information that is important to this PR  -->

* None

----

## Additional info

### Pull request checklist
- [x] Tests for the changes have been added (for bug fixes / features)
- [x] Docs have been reviewed and added / updated if needed (for bug fixes / features)
- [x] Added the appropriate label for the given change

### Does this introduce a breaking change?
<!-- If this introduces a breaking change make sure to note it here any what the impact might be -->

Please see our docs on [breaking changes](https://github.com/octokit/.github/blob/master/community/breaking_changes.md) to help!

- [ ] Yes (Please add the `Type: Breaking change` label)
- [x] No

If `Yes`, what's the impact:  

* N/A


### Pull request type

<!-- Please do not submit updates to dependencies unless it fixes an issue. --> 
<!-- Please try to limit your pull request to one type, submit multiple pull requests if needed. --> 

Please add the corresponding label for change this PR introduces:
- Bugfix: `Type: Bug`

----



Patch:
diff --git a/src/methods/get-web-flow-authorization-url.ts b/src/methods/get-web-flow-authorization-url.ts
index eb4c2e7..b103a42 100644
--- a/src/methods/get-web-flow-authorization-url.ts
+++ b/src/methods/get-web-flow-authorization-url.ts
@@ -17,11 +17,27 @@ export function getWebFlowAuthorizationUrlWithState(
   state: State,
   options: any
 ): any {
+  let allowSignup: boolean;
+  if (options.allowSignup === undefined && state.allowSignup === undefined) {
+    allowSignup = true;
+  } else if (
+    options.allowSignup === undefined &&
+    state.allowSignup !== undefined
+  ) {
+    allowSignup = state.allowSignup;
+  } else if (
+    state.allowSignup === undefined &&
+    options.allowSignup !== undefined
+  ) {
+    allowSignup = options.allowSignup;
+  } else {
+    allowSignup = options.allowSignup || state.allowSignup;
+  }
   const optionsWithDefaults = {
     clientId: state.clientId,
     request: state.octokit.request,
     ...options,
-    allowSignup: options.allowSignup || state.allowSignup,
+    allowSignup,
     scopes: options.scopes || state.defaultScopes,
   };
 
diff --git a/src/middleware/handle-request.ts b/src/middleware/handle-request.ts
index 87ac5b9..35fca35 100644
--- a/src/middleware/handle-request.ts
+++ b/src/middleware/handle-request.ts
@@ -76,7 +76,9 @@ export async function handleRequest(
       const { url } = app.getWebFlowAuthorizationUrl({
         state: query.state,
         scopes: query.scopes ? query.scopes.split(",") : undefined,
-        allowSignup: query.allowSignup !== "false",
+        allowSignup: query.allowSignup
+          ? query.allowSignup === "true"
+          : undefined,
         redirectUrl: query.redirectUrl,
       });
 


Test patch:
diff --git a/test/node-middleware.test.ts b/test/node-middleware.test.ts
index 6ab064e..d36493a 100644
--- a/test/node-middleware.test.ts
+++ b/test/node-middleware.test.ts
@@ -954,4 +954,130 @@ describe("createNodeMiddleware(app)", () => {
 
     expect(status).toEqual(302);
   });
+
+  it("GET /api/github/oauth/login with allowSignup set to false", async () => {
+    const app = new OAuthApp({
+      clientId: "0123",
+      clientSecret: "0123secret",
+      allowSignup: false,
+    });
+
+    const server = createServer(createNodeMiddleware(app)).listen();
+    // @ts-expect-error complains about { port } although it's included in returned AddressInfo interface
+    const { port } = server.address();
+
+    const { status, headers } = await fetch(
+      `http://localhost:${port}/api/github/oauth/login`,
+      {
+        redirect: "manual",
+      }
+    );
+
+    server.close();
+
+    expect(status).toEqual(302);
+
+    const url = new URL(headers.get("location") as string);
+    expect(url).toMatchObject({
+      origin: "https://github.com",
+      pathname: "/login/oauth/authorize",
+    });
+    expect(url.searchParams.get("client_id")).toEqual("0123");
+    expect(url.searchParams.get("state")).toMatch(/^\w+$/);
+    expect(url.searchParams.get("allow_signup")).toEqual("false");
+  });
+
+  it("GET /api/github/oauth/login?allowSignup=false with allowSignup not set", async () => {
+    const app = new OAuthApp({
+      clientId: "0123",
+      clientSecret: "0123secret",
+    });
+
+    const server = createServer(createNodeMiddleware(app)).listen();
+    // @ts-expect-error complains about { port } although it's included in returned AddressInfo interface
+    const { port } = server.address();
+
+    const { status, headers } = await fetch(
+      `http://localhost:${port}/api/github/oauth/login?allowSignup=false`,
+      {
+        redirect: "manual",
+      }
+    );
+
+    server.close();
+
+    expect(status).toEqual(302);
+
+    const url = new URL(headers.get("location") as string);
+    expect(url).toMatchObject({
+      origin: "https://github.com",
+      pathname: "/login/oauth/authorize",
+    });
+    expect(url.searchParams.get("client_id")).toEqual("0123");
+    expect(url.searchParams.get("state")).toMatch(/^\w+$/);
+    expect(url.searchParams.get("allow_signup")).toEqual("false");
+  });
+
+  it("GET /api/github/oauth/login?allowSignup=false with allowSignup set to true", async () => {
+    const app = new OAuthApp({
+      clientId: "0123",
+      clientSecret: "0123secret",
+      allowSignup: true,
+    });
+
+    const server = createServer(createNodeMiddleware(app)).listen();
+    // @ts-expect-error complains about { port } although it's included in returned AddressInfo interface
+    const { port } = server.address();
+
+    const { status, headers } = await fetch(
+      `http://localhost:${port}/api/github/oauth/login?allowSignup=false`,
+      {
+        redirect: "manual",
+      }
+    );
+
+    server.close();
+
+    expect(status).toEqual(302);
+
+    const url = new URL(headers.get("location") as string);
+    expect(url).toMatchObject({
+      origin: "https://github.com",
+      pathname: "/login/oauth/authorize",
+    });
+    expect(url.searchParams.get("client_id")).toEqual("0123");
+    expect(url.searchParams.get("state")).toMatch(/^\w+$/);
+    expect(url.searchParams.get("allow_signup")).toEqual("true");
+  });
+
+  it("GET /api/github/oauth/login with allowSignup not set", async () => {
+    const app = new OAuthApp({
+      clientId: "0123",
+      clientSecret: "0123secret",
+    });
+
+    const server = createServer(createNodeMiddleware(app)).listen();
+    // @ts-expect-error complains about { port } although it's included in returned AddressInfo interface
+    const { port } = server.address();
+
+    const { status, headers } = await fetch(
+      `http://localhost:${port}/api/github/oauth/login`,
+      {
+        redirect: "manual",
+      }
+    );
+
+    server.close();
+
+    expect(status).toEqual(302);
+
+    const url = new URL(headers.get("location") as string);
+    expect(url).toMatchObject({
+      origin: "https://github.com",
+      pathname: "/login/oauth/authorize",
+    });
+    expect(url.searchParams.get("client_id")).toEqual("0123");
+    expect(url.searchParams.get("state")).toMatch(/^\w+$/);
+    expect(url.searchParams.get("allow_signup")).toEqual("true");
+  });
 });