Skip to content
This repository was archived by the owner on Apr 19, 2023. It is now read-only.

Commit 93a0b82

Browse files
✨ Add geolocation
1 parent 82d34a1 commit 93a0b82

File tree

9 files changed

+81
-9
lines changed

9 files changed

+81
-9
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@types/dotenv": "^6.1.1",
2121
"@types/express": "^4.16.1",
2222
"@types/fs-extra": "^5.0.5",
23+
"@types/geolite2": "^1.2.0",
2324
"@types/jest": "^24.0.12",
2425
"@types/jsonwebtoken": "^8.3.2",
2526
"@types/marked": "^0.6.5",
@@ -44,10 +45,12 @@
4445
"express": "^4.16.4",
4546
"express-async-handler": "^1.1.4",
4647
"fs-extra": "^7.0.1",
48+
"geolite2": "^1.2.1",
4749
"googleapis": "^39.2.0",
4850
"ip-anonymize": "^0.0.6",
4951
"jsonwebtoken": "^8.5.1",
5052
"marked": "^0.6.2",
53+
"maxmind": "^3.0.3",
5154
"mustache": "^3.0.1",
5255
"mysql": "^2.17.1",
5356
"node-cache": "^4.2.0",

src/crud/event.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Event } from "../interfaces/tables/events";
33
import { Locals } from "../interfaces/general";
44
import { deleteItemFromCache, cachedQuery } from "../helpers/cache";
55
import { CacheCategories } from "../interfaces/enum";
6+
import { addLocationToEvents } from "../helpers/location";
67

78
/*
89
* Create a new security event
@@ -39,12 +40,12 @@ export const getUserEvents = async (userId: number) => {
3940
* Get the 10 most recent security events for a user
4041
*/
4142
export const getUserRecentEvents = async (userId: number) => {
42-
return <Event[]>(
43+
return await addLocationToEvents(<Event[]>(
4344
await cachedQuery(
4445
CacheCategories.USER_RECENT_EVENTS,
4546
userId,
4647
`SELECT * FROM events WHERE userId = ? ORDER BY id DESC LIMIT 10`,
4748
[userId]
4849
)
49-
);
50+
));
5051
};

src/helpers/location.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import maxmind, { CityResponse } from "maxmind";
2+
import geoLite2 from "geolite2";
3+
import { Event } from "../interfaces/tables/events";
4+
import { getItemFromCache, storeItemInCache } from "./cache";
5+
import { CacheCategories } from "../interfaces/enum";
6+
7+
export const getGeolocationFromIp = async (ipAddress: string) => {
8+
const cachedLookup = getItemFromCache(CacheCategories.IP_LOOKUP, ipAddress);
9+
if (cachedLookup) return cachedLookup;
10+
const lookup = await maxmind.open<CityResponse>(geoLite2.paths.city);
11+
const ipLookup = lookup.get(ipAddress);
12+
if (!ipLookup) return;
13+
const location: any = {};
14+
if (ipLookup.city) location.city = ipLookup.city.names.en;
15+
if (ipLookup.continent) location.continent = ipLookup.continent.names.en;
16+
if (ipLookup.country) location.country_code = ipLookup.country.iso_code;
17+
if (ipLookup.location) location.latitude = ipLookup.location.latitude;
18+
if (ipLookup.location) location.longitude = ipLookup.location.longitude;
19+
if (ipLookup.location) location.time_zone = ipLookup.location.time_zone;
20+
if (ipLookup.location)
21+
location.accuracy_radius = ipLookup.location.accuracy_radius;
22+
if (ipLookup.postal) location.zip_code = ipLookup.postal.code;
23+
if (ipLookup.subdivisions)
24+
location.region_name = ipLookup.subdivisions[0].names.en;
25+
if (ipLookup.subdivisions)
26+
location.region_code = ipLookup.subdivisions[0].iso_code;
27+
storeItemInCache(CacheCategories.IP_LOOKUP, ipAddress, location);
28+
return location;
29+
};
30+
31+
export const addLocationToEvents = async (events: Event[]) => {
32+
for await (let event of events) {
33+
event = await addLocationToEvent(event);
34+
}
35+
return events;
36+
};
37+
38+
export const addLocationToEvent = async (event: Event) => {
39+
if (event.ipAddress) {
40+
event.location = await getGeolocationFromIp(event.ipAddress);
41+
}
42+
return event;
43+
};

src/interfaces/enum.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ export enum CacheCategories {
8181
MEMBERSHIP = "membership",
8282
ORGANIZATION = "organization",
8383
APPROVE_LOCATIONS = "approved-locations",
84-
APPROVE_LOCATION = "approved-location"
84+
APPROVE_LOCATION = "approved-location",
85+
IP_LOOKUP = "ip-lookup"
8586
}
8687

8788
export enum Authorizations {

src/interfaces/tables/events.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { EventType } from "../enum";
2+
import { CityResponse } from "maxmind";
23

34
export interface Event {
45
id?: number;
@@ -9,4 +10,5 @@ export interface Event {
910
ipAddress?: string;
1011
userAgent?: string;
1112
createdAt?: Date;
13+
location?: CityResponse | null;
1214
}

src/templates/email-verify.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ Dear {{name}},
44

55
Click on the following button to verify this email for your account.
66

7-
<a style="background: #555; padding: 1rem 2rem; font-size: 120%; color: #fff; display: inline-block; margin: 2rem auto; border-radius: 0.25rem" href="{{frontendUrl}}/auth/email-verify?token={{token}}">Verify your email</a>
7+
<a style="background: #555; padding: 1rem 2rem; font-size: 120%; color: #fff; display: inline-block; margin: 2rem auto; border-radius: 0.25rem" href="{{frontendUrl}}/auth/token?subject=email-verify&token={{token}}">Verify your email</a>
88

9-
If the above link doesn't work, you can copy and paste this URL in your browser: {{frontendUrl}}/auth/email-verify?token={{token}}.
9+
If the above link doesn't work, you can copy and paste this URL in your browser: {{frontendUrl}}/auth/token?subject=email-verify&token={{token}}.
1010

1111
If you didn't request this email, you can safely ignore it.
1212

src/templates/password-reset.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ Dear {{name}},
44

55
Click on the following button to reset your password:
66

7-
<a style="background: #555; padding: 1rem 2rem; font-size: 120%; color: #fff; display: inline-block; margin: 2rem auto; border-radius: 0.25rem" href="{{frontendUrl}}/auth/reset-password?token={{token}}">Reset your password</a>
7+
<a style="background: #555; padding: 1rem 2rem; font-size: 120%; color: #fff; display: inline-block; margin: 2rem auto; border-radius: 0.25rem" href="{{frontendUrl}}/auth/token?subject=reset-password&token={{token}}">Reset your password</a>
88

9-
If the above link doesn't work, you can copy and paste this URL in your browser: {{frontendUrl}}/auth/reset-password?token={{token}}.
9+
If the above link doesn't work, you can copy and paste this URL in your browser: {{frontendUrl}}/auth/token?subject=reset-password&token={{token}}.
1010

1111
If you didn't request this email, you can safely ignore it.

src/templates/unapproved-location.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ Dear {{name}},
44

55
You tried to log in from a new location.
66

7-
<a style="background: #555; padding: 1rem 2rem; font-size: 120%; color: #fff; display: inline-block; margin: 2rem auto; border-radius: 0.25rem" href="{{frontendUrl}}/auth/location-verify?token={{token}}">Approve this login</a>
7+
<a style="background: #555; padding: 1rem 2rem; font-size: 120%; color: #fff; display: inline-block; margin: 2rem auto; border-radius: 0.25rem" href="{{frontendUrl}}/auth/token?subject=approve-location&token={{token}}">Approve this login</a>
88

9-
If the above link doesn't work, you can copy and paste this URL in your browser: {{frontendUrl}}/auth/location-verify?token={{token}}.
9+
If the above link doesn't work, you can copy and paste this URL in your browser: {{frontendUrl}}/auth/token?subject=approve-location&token={{token}}.
1010

1111
If you didn't just login, we highly recommend you change your password.

yarn.lock

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,11 @@
386386
dependencies:
387387
"@types/node" "*"
388388

389+
"@types/geolite2@^1.2.0":
390+
version "1.2.0"
391+
resolved "https://registry.yarnpkg.com/@types/geolite2/-/geolite2-1.2.0.tgz#f66dcdd395e5be29dff5d8c476c4cfb015a5e0d7"
392+
integrity sha512-fSEel9sfeC8XGS/CD8duQhaGtAS9Ho3ehVEzAUZWFWpSiHKYX4fkJleEy08qbpF5tU4AnMHGGVKi0EvpI6tYag==
393+
389394
"@types/glob@*":
390395
version "7.1.1"
391396
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
@@ -1821,6 +1826,11 @@ gcp-metadata@^1.0.0:
18211826
gaxios "^1.0.2"
18221827
json-bigint "^0.3.0"
18231828

1829+
geolite2@^1.2.1:
1830+
version "1.2.1"
1831+
resolved "https://registry.yarnpkg.com/geolite2/-/geolite2-1.2.1.tgz#b7c62d182021d398f7fc3c9d09a01c067b36cb36"
1832+
integrity sha512-4NqR+Wr4/KMSDrTq+0jnxL+xiMBiUXFiLBuMCs5gChLhZD6uvWICQxQV3MNpRH3FPGnB4gKHs3gGaYd+6X4sGQ==
1833+
18241834
get-caller-file@^1.0.1:
18251835
version "1.0.3"
18261836
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
@@ -3202,6 +3212,13 @@ marked@^0.6.2:
32023212
resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.2.tgz#c574be8b545a8b48641456ca1dbe0e37b6dccc1a"
32033213
integrity sha512-LqxwVH3P/rqKX4EKGz7+c2G9r98WeM/SW34ybhgNGhUQNKtf1GmmSkJ6cDGJ/t6tiyae49qRkpyTw2B9HOrgUA==
32043214

3215+
maxmind@^3.0.3:
3216+
version "3.0.3"
3217+
resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-3.0.3.tgz#f771af8cc10a6a3481a42c3f374af9448a0689c8"
3218+
integrity sha512-E/AvmqI1bfYU1c8p+Q7xCc9TGoTOp/yV8WsUHuJ+eGYEHmakEazbl3OrJCYkacBdLEqq19p/G3OyjBSDUJTUqA==
3219+
dependencies:
3220+
tiny-lru "6.0.1"
3221+
32053222
media-typer@0.3.0:
32063223
version "0.3.0"
32073224
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -4651,6 +4668,11 @@ timed-out@^4.0.0:
46514668
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
46524669
integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
46534670

4671+
tiny-lru@6.0.1:
4672+
version "6.0.1"
4673+
resolved "https://registry.yarnpkg.com/tiny-lru/-/tiny-lru-6.0.1.tgz#558bd6b7232b8b9dfa482147539676fdd75a3a07"
4674+
integrity sha512-k/vdHz+bFALjmik0URLWBYNuO0hCABTL5dullbZBXvFDdlL8RrKaeLR6YuHfX+6ZXOLkHw+HpNLCUA7DtLMQmg==
4675+
46544676
tmpl@1.0.x:
46554677
version "1.0.4"
46564678
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"

0 commit comments

Comments
 (0)