npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

impossible-travel-guard

v0.1.2

Published

Flag account logins that imply physically impossible travel speed between two locations. A missing building block for account-takeover detection, works with any geolocation provider.

Readme

impossible-travel-guard

Catch account takeover by checking if a login is even physically possible.

Someone logs into an account from New York. Forty minutes later, the same account logs in from Lagos. No human made that trip, so the second login should never be trusted the same as the first. That is the entire idea behind this library, and it is a check almost no app runs today.

New York login at 09:00
Lagos login at 09:40
8,400 km apart, 40 minutes apart, implied speed 12,000+ km/h
=> flagged

Who this is for

Developers building login or authentication for an app, SaaS product, or e-commerce site, who want to automatically catch account takeover without hand-rolling this logic themselves.

What it actually does

Every geolocation API, including IPGeolocation.io, gives you a location for one IP address. None of them ship the part that comes after that: remembering a user's last login, comparing it to their new one, and telling you if a human could plausibly have made that trip in the time between. This library is that missing piece.

It is one of the few account-takeover signals that compares a user against their own history instead of a blacklist. That means it still works even when both IP addresses look perfectly clean on their own, no VPN flag, no bad reputation, nothing else suspicious about either login individually. The only thing wrong is the sequence.

Try it live, no install needed

Open the live demo, pick two cities and a time gap, and see it flag (or clear) a login pair in your browser: https://furqan-ashraf.github.io/impossible-travel-guard/

How it helps you

  • Drops into an existing login flow in about five lines of code.
  • Zero runtime dependencies, small enough to read in one sitting.
  • Works with whatever geolocation provider you already use, latitude and longitude in, a decision out.
  • Ships with a pluggable storage interface, so it fits whatever database or cache you already run.
  • Comes with a tested, working example instead of a blog post that only describes the logic.

Install

npm install impossible-travel-guard

Quick start

import { TravelGuard } from "impossible-travel-guard";

const guard = new TravelGuard(); // in-memory store by default

const result = await guard.check({
  userId: "user_123",
  latitude: 6.5244,
  longitude: 3.3792,
  timestamp: Date.now(),
});

if (result.flagged) {
  // require MFA, send an alert, or hold the session for review
  // do not hard-block on this signal alone, see "How to act on a flag" below
}

The first login for any userId is never flagged, there is nothing to compare it to yet. It is just recorded as the baseline.

Getting latitude/longitude from an IP

This library does not call any geolocation API itself, on purpose, so it works with whatever provider you already have. If you don't have one, examples/ipgeolocation-adapter.ts shows a working example using IPGeolocation.io's IP location API, free tier covers the lookup this needs.

import { loginEventFromIp } from "./examples/ipgeolocation-adapter";

const event = await loginEventFromIp(userId, req.ip);
const result = await guard.check(event);

Testing this with real IP addresses, not sample data

examples/live-test.ts runs the whole flow end to end with two real IP addresses and a real IPGeolocation.io key, exactly what your sign-in form's backend would do, no hardcoded coordinates.

# 1. Get a free key at https://ipgeolocation.io
# 2. Set it as an environment variable (never hardcode a key in your code)
export IPGEO_API_KEY="your-key-here"

# 3. Run it with two real IPs and the minutes between logins
npx tsx examples/live-test.ts 8.8.8.8 197.210.28.1 40

This resolves both IPs to real coordinates, runs the same check as guard.check(), and prints the distance, elapsed time, implied speed, and whether it would be flagged. Note this only works from a backend/script, never call the geolocation API directly from a browser, that exposes your API key to anyone who views the page source.

Configuration

new TravelGuard({
  maxPlausibleSpeedKmh: 1000, // default, comfortably above commercial flight speed
  minDistanceKm: 50,          // default, ignores GPS/IP jitter within the same metro area
  store: myCustomStore,       // default is an in-memory Map, see below
});

Tune maxPlausibleSpeedKmh down if your users are not typically international travelers, or up if you'd rather only catch the most extreme cases.

Storage

The default MemoryStore works for a demo or a single-process app, and loses everything on restart. For production, implement the two-method LoginStore interface against Redis, Postgres, or whatever you already run:

import { LoginStore, LoginEvent } from "impossible-travel-guard";

class RedisStore implements LoginStore {
  async getLastLogin(userId: string): Promise<LoginEvent | null> {
    const raw = await redis.get(`last_login:${userId}`);
    return raw ? JSON.parse(raw) : null;
  }
  async saveLogin(event: LoginEvent): Promise<void> {
    await redis.set(`last_login:${event.userId}`, JSON.stringify(event));
  }
}

const guard = new TravelGuard({ store: new RedisStore() });

How to act on a flag

Treat a flag as a prompt for step-up verification, not an automatic block. Real users hop networks, use VPNs, or have their IP misresolved by CGNAT often enough that a hard block on this signal alone will lock out legitimate accounts. Reasonable responses, in order of severity: log it, email the user, require MFA, hold the session for manual review. Reserve an outright block for when this signal stacks with others (new device, new browser fingerprint, password reset requested minutes earlier).

What this does not do

This is one signal, not a fraud engine. It has no opinion on VPNs, proxies, device fingerprints, or IP reputation, pair it with those if you need them. It also cannot tell you why a trip is impossible, just that it is, a shared corporate VPN egress point flipping between two data centers can trigger a false positive too. Log enough context to review flags by hand until you trust your thresholds.

Development

Clone the repo, install dependencies, then build and test:

git clone https://github.com/Furqan-Ashraf/impossible-travel-guard.git
cd impossible-travel-guard
npm install
npm run build
npm test

All 5 tests should pass (same-city logins ignored, plausible flights pass, physically impossible sequences flagged). Tests live in test/travelGuard.test.ts and run on Node's built-in test runner via tsx, no extra test framework to install.

API

new TravelGuard(options?)

See Configuration above.

guard.check(event: LoginEvent): Promise<CheckResult>

| Field | Type | Meaning | | :-- | :-- | :-- | | flagged | boolean | Whether this login was flagged | | reason | string | first_login, ok, too_close_to_matter, or impossible_travel | | distanceKm | number | null | Distance from the previous login | | elapsedHours | number | null | Time since the previous login | | impliedSpeedKmh | number | null | distanceKm / elapsedHours | | previousLogin | LoginEvent | null | The login this was compared against | | currentLogin | LoginEvent | The event that was just checked |

Contributing

Issues and pull requests are welcome. If you find a false positive or false negative worth handling differently, open an issue with the two login events involved, real numbers make it easy to reason about.

License

MIT