class

ExpressAuthTagger

Inherits PrefixScope < FrameworkTagger < FileHelper < Tagger < Reference < Object

Constants

ANONYMOUS_PASS_THROUGH = /\bif\s*\(\s*!\s*req\.[^)]*\)\s*\{?[^{}]*?\breturn\s+next\s*\(\s*\)/

if (!req.headers.authorization) { return next(); }: a caller without the credential is let through before anything can refuse it.

AUTH_MIDDLEWARE_NAMES = [/\brequireAuth\b/, /\bisAuth\b/, /\bisAuthenticated\b/, /\bensureLoggedIn\b/, /\bensureAuthenticated\b/, /\bauthorize\b/, /\brequireLogin\b/, /\bauthMiddleware\b/, /\bverifyToken\b/, /\bcheckAuth\b/]
BARE_IDENT = /\A[A-Za-z_$][\w$]*\z/
BARE_NEXT = /\bnext\s*\(\s*\)/
BARE_USE = /\b(\w+)\.use\s*\(/

x.use(authMiddleware) — no path argument. Which routes this guards is a property of the object x, not of any URL, so the receiver is captured.

BASIC_AUTH_IMPORT = /(?:\bimport\s+([A-Za-z_$][\w$]*)\s+from|\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*require\s*\()\s*['"]express-basic-auth['"]/

The name an express-basic-auth import is bound to in a file.

BRANCH_REFUSALS = {/\bthrow\b/ => "throws", /\b(?:res|response)\s*\.\s*sendStatus\s*\(\s*40[13]\b|\b(?:res|response)\s*\.\s*status\s*\(\s*40[13]\s*\)\s*\.\s*(?:json|send|end|render)\s*\(/ => "answers 401/403", /\bnext\s*\(\s*[^)\s'"`]/ => "next(err)", /\b(?:res|response)\s*\.\s*redirect\s*\(/ => "redirects"}

What the guarded branch does to the caller it stops, and how that reads in the tag: throws (the app's error handler answers), answers 401/403 (res.status(401) alone sends nothing), hands an error to next(err), or redirects to a login page.

CALLER_FIELD = /\A(?:user|userId|user_id|uid|currentUser|account|accountId|principal|identity|isAuthenticated|isLoggedIn|loggedIn)\z/

The last field of a negated read that names who is calling: !req.user, !req.accountability?.user, !req.session.userId, !req.isAuthenticated(). The first field must not be request data (!req.params.userId refuses a bad URL, not an anonymous caller).

CREDENTIALS_OPTIONAL = /credentialsRequired\s*:\s*false\b/
DATA_ROOTS = /\A(?:params|query|body|headers?|cookies|signedCookies|files?|schema|method|path|url|originalUrl|baseUrl|ip|ips|hostname|protocol|get|is|accepts|collection|sanitizedQuery|route|app|res)\z/

Request fields that carry what the caller sent or what the app looked up, not who the caller is: a check on them refuses bad input.

EARLIER_EXIT = /\breturn\b|\bawait\b|\bnext\s*\(|\b(?:res|response)\s*\.\s*(?:json|send|sendStatus|status|render|redirect|end|sendFile|write)\s*\(/

Anything that can answer, pass the request on or change state before a later check runs; a check after one of these is not what every caller meets first.

ERROR_HANDLER_SIGNATURE = /\(\s*err\w*\s*[,:]/

(err, req, res, next): an error handler answers 401 for a guard; it is not one.

HANDLER_RESPONSE = /\b(?:res|response)\s*\.\s*(?:json|send|sendStatus|render|redirect|sendFile|download|attachment|end|status)\s*\(|\.pipe\s*\(\s*(?:res|response)\s*\)/

A definition that answers the request itself, refusals aside: in the handler position that is the handler (login() answers 401 to a wrong password), not a guard in front of one.

HELPER_CALL = /\A\s*(?:await\s+)?((?:[A-Za-z_$][\w$]*\s*\.\s*)*([A-Za-z_$][\w$]*))\s*\(/
HELPER_FORBIDDEN = /\b[A-Za-z_$][\w$]*\s*\(\s*(?:res|response)\s*,\s*403\b/

Where code runs an express-basic-auth middleware: the factory (as the file imports it) called and the result called on the request (basicAuth({…})(req, res, next)) or returned, or the variable it is assigned to called on the request or returned. Each is a 401 for a caller without credentials, under the conditions around the run: one built and never run, or a local basicAuth({ user, pass }) that builds an outbound header, refuses nothing.

IDENTITY_THROW = /\bthrow\s+new\s+(?:[\w$]+\.)*\w*(?:Unauthori[sz]ed|Forbidden|InvalidCredentials|Unauthenticated|NotAuthenticated|PermissionDenied|NoPermission)\w*/

throw new ForbiddenError() under an if that reads who the caller is (if (!isAdmin(req.accountability))): every anonymous caller takes it. A throw on request data (req.params.collection in req.schema…) is a 4xx for bad input, not a guard.

IF_HEAD = /(?<![\w$.])if\s*\(/
INPUT_REFUSAL_CALL = /\A\{\s*return\s+([A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*)\s*\([^;{}]*\)\s*;?\s*\}\z/
JS_WORDS = /\A(?:true|false|null|undefined|typeof|instanceof|in|new|void|this)\z/
JWT_FACTORY = /\Aexpress[jJ]wt\z/
JWT_MIDDLEWARE_PATTERNS = [/expressjwt\s*\(/, /expressJwt\s*\(/]
LITERAL_ARG = /\A\s*(?:'[^']*'|"[^"]*"|`[^`]*`)\s*\z/
MAX_ARRAY_DEPTH = 4
MAX_CHAIN_LOOKBACK = 500
MAX_DEFINITION_LINES = 40
MAX_STATEMENT_LINES = 60

ponytail: brace/paren-counted spans; a registration whose closure runs past 60 lines or a definition past 40 is read truncated, not re-parsed.

METHOD_TEST = /\A\(*\s*(?:req|request)\s*\.\s*method\s*(!==?|===?)\s*['"]([A-Za-z]+)['"]\s*\)*\z/
MISSING_CALLER = /!\s*(?:req|request)\s*\.\s*(?:(?:session\s*\??\.\s*)?(?:user\w*|uid|account\w*|auth\w*|token\w*|logged\w*|isAuthenticated|passport|identity|principal)\b|session\s*(?:\)|\|\||&&))/

!req.user, !req.session.userId, !req.session: no caller. Not !req.session.cart, a session without a cart.

MOUNT_CALL = /\.\s*(?:use|all)\s*\(/

The calls the mount pre-scan reads: a file with neither is skipped whole.

PASSPORT_LOGIN_STRATEGY = /\A(?:local\w*|google\w*|github|gitlab|facebook|twitter|linkedin\w*|microsoft|azuread-openidconnect|apple|discord|slack|auth0|okta|oauth2?|openidconnect|oidc|saml)\z/i

Passport strategies that are the login flow — local checks the submitted password on POST /login, the OAuth/OIDC/SAML ones start or finish a provider redirect. The route they sit on is the anonymous entry point, not a protected resource.

PASSPORT_PATTERN = /passport\.authenticate\s*\(/
PATH_ARG = /\A\s*['"`]\//
PATH_EQUALS = /\A\(*\s*(?:req|request)\s*\.\s*(?:path|url|originalUrl|baseUrl)\s*===?\s*(['"])(\/[^'"]*)\1\s*\)*\z/
PATH_FIELD = /(?<![\w$.])(?:req|request)\s*\.\s*(?:path|url|originalUrl|baseUrl|route\s*\.\s*path)\b/
PATH_LITERAL = /\A(['"])\/[^'"]*\1\z/
PRESENCE_CONJUNCTION = /\A\s*[\w$.?]+\s*&&/

if (req.member && req.member.banned): the refusal needs something present first, which an anonymous caller does not have.

PROMISE_ALL = /\bPromise\s*\.\s*all\s*\(/

Promise.all( / Promise.all (: the builtin, never a mount. A large TS codebase carries thousands of them, and each one used to be read as a .all() registration whose every argument was followed to its definition (n8n: ~3,200 lines, over a second of tagging).

QUIET_AWAIT = /\bawait\s+[A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*\s*\((?:[^()]|\([^()]*\))*\)/
REFUSING_DEFINITION = /\bexpress[jJ]wt\s*\(|\bpassport\.authenticate\s*\(|\b[A-Za-z_$][\w$]*\s*\(\s*(?:res|response)\s*,\s*401\b|(?:\b\w+\.)?(?:status|sendStatus|send)\s*\(\s*40[13]\s*\)?|\bnext\s*\(\s*(?:new\s+)?(?:[\w$]+\.)*\w*(?:Unauthori[sz]ed|Forbidden|NoPermission|PermissionDenied)\w*|\bcreateError\s*\(\s*40[13]\b|\bnext\s*\(\s*\{[^}]*\b40[13]\b/

What a middleware's definition must be able to do to count as a guard whatever it is called: refuse the request. jwt.verify( alone is deliberately absent — a function that verifies a token and then calls next() unconditionally (juice-shop's updateAuthenticatedUsers) is not a guard, and it is app.use()d over every route.

sendError(res, 401, …) hands the response and a 401 to a reply helper (a helper's 403 is as often about the payload as the caller). An express-basic-auth middleware refuses where it is run, not where it is built: see basic_auth_runs.

ROUTE_CHAIN_START = /\b\w+\s*\.\s*route\s*\(/
ROUTE_DECLARATION = /\.\s*(?:get|post|put|patch|delete|options|head|all|use|route)\s*\(/

The start of a route registration, used as a statement boundary so a window around one route never reads the next one's middleware.

ROUTE_RECEIVER = /\b(\w+)\s*\.\s*(?:get|post|put|patch|delete|options|head|all|use|route)\s*\(/

A route registration, and who it was registered on: app.get('/x', …), router.post(…), app.route('/x').

ROUTER_ARG = /\A_?(?:router|app|server|[A-Za-z_$][\w$]*(?:Router|App|Server))\z/

The receiver a helper is handed, by the names the route-helper scanner accepts for it.

SERVICE_AWAIT = /\bawait\s+([A-Za-z_$][\w$]*)\s*\.\s*([A-Za-z_$][\w$]*)\s*\(/
SERVICE_NEW = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*new\s+([A-Z][\w$]*)\s*\(/
SETTING_NOTE = " — a setting can lift it"
SETTING_READ = /\A(?:[\w$]+\s*\.\s*)*\w*(?:[sS]ettings?|[cC]onfig)\w*\s*[.(]|\Aprocess\s*\.\s*env\b/

A read of configuration: Settings.get("disableAuth"), config.x, getSetting("y"), process.env.Z.

STATUS_4XX = /\b(?:res|response)\s*\.\s*(?:status|sendStatus)\s*\(\s*['"]?4\d\d\b|\b[A-Za-z_$][\w$.]*\s*\(\s*(?:res|response)\s*,\s*['"]?4\d\d['"]?\s*[,)]/

A 4xx answer as a definition writes it: res.status(413), res.sendStatus(400), or a reply helper handed the response and the code (responseError(res, '413', …)).

STATUS_4XX_CHAIN = /\b(?:res|response)\s*\.\s*status\s*\(\s*['"]?4\d\d['"]?\s*\)\s*\.\s*[A-Za-z]+\s*\(/

A response that is not a refusal: anything sent without a 4xx status chained before it.

STOP_CALL = /\breturn\s+(?:await\s+)?([A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*)\s*\(/
SUCCESS_RESPONSE = /\b(?:res|response)\s*\.\s*(?:json|jsonp|send|render|sendFile|download|end|write)\s*\(|\.pipe\s*\(\s*(?:res|response)\s*\)/
TOP_LEVEL_IF = /(?<![\w$.])if\s*\(/
WRAPPED_EXPORT = /\A\s*(?:export\s+default|(?:export\s+)?(?:const|let|var)\s+[\w$]+\s*(?::[^=]*)?=|module\.exports\s*=)\s*([A-Za-z_$][\w$.]*\s*\(\s*[A-Za-z_$][\w$]*\s*\))\s*;?\s*\z/

Constructors

new(options : Hash(String, YAML::Any))
Source

Class methods

target_techs
Source

Instance methods

perform(endpoints : Array(Endpoint)) : Array(Endpoint)

The per-endpoint shape: look at each endpoint, tag in place, hand the array back. Taggers declare only check_endpoint; those that need a pass over the whole set first (a project pre-scan, mcp's legacy prefixes) override perform and call super or loop themselves.

Source