Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | 44x 44x 44x 146x 44x 453x 453x 906x 453x 492x 492x 492x 492x 20x 472x 142x 12x 130x 44x | 'use strict';
/**
* SHY-0060 — JS SERVER PORT of the Kotlin SafetyGate
* (shared/src/commonMain/kotlin/com/shyden/shytalk/core/safety/SafetyGate.kt).
*
* Pure logic over a resolved verified age + region, mirroring the Kotlin engine
* exactly, so the server enforces the identical verdict the clients compute.
* Thresholds come from the parity-pinned age-thresholds mirror.
*
* Enforcement is gated by a default-OFF operator feature flag at the call sites
* (a later increment); this engine computes the verdict, it does not decide
* whether gating is switched on.
*/
const { BASE, COPPA_FLOOR, REGION_OVERRIDES, thresholdFor } = require('./age-thresholds');
/** What a block requires before the user can retry (mirror of the Kotlin enum). */
const VERIFICATION = Object.freeze({ NONE: 'NONE', REVERIFY: 'REVERIFY' });
/** GateResult constructors (mirror of the Kotlin sealed GateResult). */
const ALLOWED = Object.freeze({ type: 'Allowed' });
const blockedUnderAge = (threshold, actualAge, requiredVerification) => ({
type: 'BlockedUnderAge',
threshold,
actualAge,
requiredVerification,
});
const blockedRegion = (threshold, reason) => ({ type: 'BlockedRegion', threshold, reason });
/**
* The strictest threshold across the base value + every region override for
* `feature` — applied when the region can't be detected, so an undetected user
* never lands on a more permissive threshold than any region.
*/
function conservativeThreshold(feature) {
let max = BASE[feature];
for (const map of Object.values(REGION_OVERRIDES)) {
if (map[feature] !== undefined && map[feature] > max) max = map[feature];
}
return max;
}
/** Region-aware threshold; a null/undefined (undetected) region uses the conservative max. */
function effectiveThreshold(feature, countryCode) {
return (countryCode ?? null) === null
? conservativeThreshold(feature)
: thresholdFor(feature, countryCode);
}
/**
* Decide whether `feature` is permitted for a user of `verifiedAgeYears`
* (null/undefined when the age is unverified) in `countryCode` (ISO alpha-2;
* null/undefined when the region can't be detected → conservative threshold).
*/
function canAccess(feature, verifiedAgeYears, countryCode) {
const base = BASE[feature];
const effective = effectiveThreshold(feature, countryCode);
if ((verifiedAgeYears ?? null) === null) {
// Legacy/unverified: COPPA-floor features stay open (they cleared the
// signup-13 gate); anything stricter needs re-verification.
return effective <= COPPA_FLOOR
? ALLOWED
: blockedUnderAge(effective, null, VERIFICATION.REVERIFY);
}
if (verifiedAgeYears >= effective) return ALLOWED;
// Blocked. If the user would clear the BASE bar and only a regional rule lifts
// it above them, that's a region block; otherwise plain under-age.
if (effective > base && verifiedAgeYears >= base) {
return blockedRegion(effective, `This region requires age ${effective} for ${feature}`);
}
return blockedUnderAge(effective, verifiedAgeYears, VERIFICATION.NONE);
}
module.exports = { canAccess, conservativeThreshold, ALLOWED, VERIFICATION };
|