@produtype/core 1.24.0 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -556,6 +556,36 @@ async function detectAuth(ctx) {
556
556
  present: apiKeySignals.length > 0,
557
557
  evidence: (0, absenceEvidence_1.evidenceOrSearch)(snippetEvidence(apiKeySignals), 'keys this product issues and checks', ['x-api-key', 'api_keys.find', 'api_keys.where', 'hashedApiKey', 'token scope']),
558
558
  },
559
+ {
560
+ /**
561
+ * Whether this project's routes are in a language these patterns can read.
562
+ *
563
+ * A gestionale with `/accedi`, `/recupero-password` and `/conferma-email` —
564
+ * bcrypt, jsonwebtoken, the whole flow present — was told at `high` that it has
565
+ * no password reset and no erasure. The searches look for "forgot password",
566
+ * "password_reset" and "reset token", and `recuperoPassword` is none of those.
567
+ * Adding the Italian words would fix Italian and leave Japanese, Spanish and
568
+ * every other language exactly where they were, which is the guessing this
569
+ * analyzer exists to stop.
570
+ *
571
+ * What can be known is whether we ever read a route name at all. `routeSignals`
572
+ * matches `/login`, `/register`, `/signin` and their relatives; where a project
573
+ * authenticates — proved by a hashing package, which is not a word anybody
574
+ * chose — and *none* of those ever matched, its routes are named in something
575
+ * else. The reset search never had a chance, and the difference between "you
576
+ * have no reset" and "we could not read your routes" is the whole difference
577
+ * between a finding and a guess.
578
+ *
579
+ * A framework that supplies the flow is checked before this: Django's
580
+ * `auth.urls`, Devise and `Auth::routes(` are read whatever the routes around
581
+ * them are called.
582
+ */
583
+ key: 'auth.routesAreReadable',
584
+ present: routeSignals.length > 0,
585
+ evidence: routeSignals.length > 0
586
+ ? snippetEvidence(routeSignals.slice(0, 3))
587
+ : (0, absenceEvidence_1.searchedFor)('a route named in English', ['/login', '/register', '/signin', '/signup', '/logout']),
588
+ },
559
589
  {
560
590
  key: 'auth.passwordReset',
561
591
  present: passwordResetSignals.length > 0 || passwordResetFiles.length > 0,
@@ -3,6 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toFindingId = toFindingId;
4
4
  exports.evaluateExpectedCapabilities = evaluateExpectedCapabilities;
5
5
  const productProfiles_1 = require("./productProfiles");
6
+ /**
7
+ * Whether this project's routes are named in a language these searches can read.
8
+ *
9
+ * A gestionale with `/accedi`, `/recupero-password` and `/conferma-email` — bcrypt,
10
+ * jsonwebtoken, the whole flow present — was told at `high` that it has no password
11
+ * reset and no email verification. The searches look for "forgot password",
12
+ * "password_reset" and "verify email", and `recuperoPassword` is none of those.
13
+ * Adding the Italian words would fix Italian and leave Japanese, Spanish and every
14
+ * other language where they were, which is the guessing this analyzer exists to stop.
15
+ *
16
+ * What can be known is whether a route name was ever read at all. Where a project
17
+ * authenticates — proved by a password-hashing package, which is nobody's choice of
18
+ * word — and none of `/login`, `/register`, `/signin` ever matched, its routes are
19
+ * named in something else. The reset search never had a chance, and "we could not
20
+ * read your routes" is a different sentence from "you have no reset".
21
+ *
22
+ * Blindness may turn a verdict into no verdict; it may never turn it into the
23
+ * opposite verdict. The same rule the report applies to a language it cannot parse,
24
+ * applied to a vocabulary it cannot read.
25
+ */
26
+ function routesWentUnread(analysis) {
27
+ return detector(analysis, 'auth.core')?.present === true
28
+ && detector(analysis, 'auth.routesAreReadable')?.present === false;
29
+ }
6
30
  function detector(analysis, key) {
7
31
  return analysis.detectors[key];
8
32
  }
@@ -35,10 +59,12 @@ function deriveStatus(analysis, capability) {
35
59
  const managedOnly = detector(analysis, 'auth.externalIdentityOnly')?.present === true;
36
60
  if (managedOnly && detector(analysis, 'auth.core')?.present === true)
37
61
  return 'not_applicable';
38
- return 'missing';
62
+ return routesWentUnread(analysis) ? 'unknown' : 'missing';
39
63
  }
40
64
  case 'auth.email-verification': {
41
- return detector(analysis, 'auth.emailVerification')?.present ? 'present' : 'missing';
65
+ if (detector(analysis, 'auth.emailVerification')?.present)
66
+ return 'present';
67
+ return routesWentUnread(analysis) ? 'unknown' : 'missing';
42
68
  }
43
69
  case 'authz.roles': {
44
70
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "Deterministic CLI and library that analyzes a web application repository and reports how far it is from production-ready for the kind of product it is meant to be.",
5
5
  "license": "MIT",
6
6
  "bin": {