From 3f6e5a282c78da8fb661e3707f52afd770d0fac4 Mon Sep 17 00:00:00 2001
From: Rich Trott <rtrott@gmail.com>
Date: Sat, 19 Mar 2022 11:45:43 -0700
Subject: [PATCH] fix: correct username and token validation

The current username and token validation regular expressions will match
any string. This adds tests and fixes the regular expressions.
---
 lib/auth.js            | 32 ++++++++++++++++++++++++--------
 test/unit/auth.test.js | 37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 61 insertions(+), 8 deletions(-)

diff --git a/lib/auth.js b/lib/auth.js
index 2569b047..761d01c7 100644
--- a/lib/auth.js
+++ b/lib/auth.js
@@ -1,4 +1,3 @@
-import assert from 'node:assert';
 import fs from 'node:fs';
 import { ClientRequest } from 'node:http';
 import util from 'node:util';
@@ -11,9 +10,27 @@ const ghauth = util.promisify(ghauthBase);
 
 export default lazy(auth);
 
+function errorExit(message) {
+  process.stderr.write(`${message}\n`);
+  process.exit(1);
+}
+
 function check(username, token) {
-  assert(typeof username === 'string' && /^[a-zA-Z0-9]*/.test(username));
-  assert(typeof token === 'string' && /^[0-9a-f]*/.test(token));
+  if (typeof username !== 'string') {
+    errorExit(`username must be a string, received ${typeof username}`);
+  }
+  if (!/^[a-zA-Z0-9-]+$/.test(username)) {
+    errorExit(
+      'username may only contain alphanumeric characters or hyphens, ' +
+      `received ${username}`
+    );
+  }
+  if (typeof token !== 'string') {
+    errorExit(`token must be a string, received ${typeof token}`);
+  }
+  if (!/^[0-9a-f]+$/.test(token)) {
+    errorExit(`token must be lowercase hexadecimal, received ${token}`);
+  }
 }
 
 function lazy(fn) {
@@ -36,8 +53,7 @@ async function tryCreateGitHubToken(githubAuth) {
       note: 'node-core-utils CLI tools'
     });
   } catch (e) {
-    process.stderr.write(`Could not get token: ${e.message}\n`);
-    process.exit(1);
+    errorExit(`Could not get token: ${e.message}`);
   }
   return credentials;
 }
@@ -84,11 +100,11 @@ async function auth(
   if (options.jenkins) {
     const { username, jenkins_token } = getMergedConfig();
     if (!username || !jenkins_token) {
-      process.stdout.write(
+      errorExit(
         'Get your Jenkins API token in https://ci.nodejs.org/me/configure ' +
         'and run the following command to add it to your ncu config: ' +
-        'ncu-config --global set jenkins_token TOKEN\n');
-      process.exit(1);
+        'ncu-config --global set jenkins_token TOKEN'
+      );
     };
     check(username, jenkins_token);
     result.jenkins = encode(username, jenkins_token);
diff --git a/test/unit/auth.test.js b/test/unit/auth.test.js
index ea68aeae..589de00b 100644
--- a/test/unit/auth.test.js
+++ b/test/unit/auth.test.js
@@ -70,6 +70,43 @@ describe('auth', async function() {
       'Could not get token: Bad credentials\n', 'run-auth-error'
     );
   });
+
+  it('does not accept a non-string username', async function() {
+    this.timeout(2000);
+    await runAuthScript(
+      { HOME: { username: {}, token: '0123456789abcdef' } },
+      [],
+      'username must be a string, received object\n'
+    );
+  });
+
+  it('does not accept a non-string token', async function() {
+    this.timeout(2000);
+    await runAuthScript(
+      { HOME: { username: 'nyancat', token: 42 } },
+      [],
+      'token must be a string, received number\n'
+    );
+  });
+
+  it('does not accept an invalid username format', async function() {
+    this.timeout(2000);
+    await runAuthScript(
+      { HOME: { username: ' ^^^ ', token: '0123456789abcdef' } },
+      [],
+      'username may only contain alphanumeric characters or hyphens, ' +
+      'received  ^^^ \n'
+    );
+  });
+
+  it('does not accept an invalid token format', async function() {
+    this.timeout(2000);
+    await runAuthScript(
+      { HOME: { username: 'nyancat', token: '0123456789ABCDEF' } },
+      [],
+      'token must be lowercase hexadecimal, received 0123456789ABCDEF\n'
+    );
+  });
 });
 
 // ncurc: { HOME: 'text to put in home ncurc',