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

Commit

Permalink
♻️ Add cache to geolocation
Browse files Browse the repository at this point in the history
  • Loading branch information
AnandChowdhary committed Oct 30, 2020
1 parent ac22501 commit d0bb693
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 2 deletions.
3 changes: 3 additions & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ export default () => ({
meta: {
totpServiceName: process.env.TOPT_SERVICE_NAME ?? 'Staart',
},
caching: {
geolocationLruSize: process.env.GEOLOCATION_LRU_SIZE ?? 100,
},
security: {
saltRounds: process.env.SALT_ROUNDS ?? 10,
jwtSecret: process.env.JWT_SECRET ?? 'staart',
Expand Down
2 changes: 2 additions & 0 deletions src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ConfigModule } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { EmailModule } from '../email/email.module';
import { GeolocationModule } from '../geolocation/geolocation.module';
import { PrismaModule } from '../prisma/prisma.module';
import { PwnedModule } from '../pwned/pwned.module';
import { TokensModule } from '../tokens/tokens.module';
Expand All @@ -18,6 +19,7 @@ import { JwtStrategy } from './jwt.strategy';
TokensModule,
ConfigModule,
PwnedModule,
GeolocationModule,
JwtModule.register({
secret: process.env.JWT_SECRET ?? 'staart',
}),
Expand Down
13 changes: 12 additions & 1 deletion src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { TokensService } from '../tokens/tokens.service';
import { RegisterDto } from './auth.dto';
import { AccessTokenClaims } from './auth.interface';
import anonymize from 'ip-anonymize';
import { GeolocationService } from '../geolocation/geolocation.service';

@Injectable()
export class AuthService {
Expand All @@ -42,6 +43,7 @@ export class AuthService {
private jwtService: JwtService,
private pwnedService: PwnedService,
private tokensService: TokensService,
private geolocationService: GeolocationService,
) {
this.authenticator = authenticator.create({
window: [
Expand Down Expand Up @@ -366,12 +368,21 @@ export class AuthService {
select: { name: true, prefersEmail: true },
});
if (!user) throw new NotFoundException('User not found');
const location = await this.geolocationService.getLocation(ipAddress);
const locationName =
[
location?.city?.names?.en,
location?.subdivisions[0]?.names?.en,
location?.country?.names?.en,
]
.filter(i => i)
.join(', ') || 'Unknown location';
this.email.send({
to: `"${user.name}" <${user.prefersEmail.emailSafe}>`,
template: 'auth/approve-subnets',
data: {
name: user.name,
subnet,
locationName,
minutes: 30,
link: `${this.configService.get<string>(
'frontendUrl',
Expand Down
2 changes: 2 additions & 0 deletions src/modules/geolocation/geolocation.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { GeolocationService } from './geolocation.service';

@Module({
imports: [ConfigModule],
providers: [GeolocationService],
exports: [GeolocationService],
})
Expand Down
16 changes: 16 additions & 0 deletions src/modules/geolocation/geolocation.service.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import { Injectable, OnModuleDestroy } from '@nestjs/common';
import maxmind, { Reader, CityResponse } from 'maxmind';
import geolite2 from 'geolite2-redist';
import QuickLRU from 'quick-lru';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class GeolocationService implements OnModuleDestroy {
constructor(private configService: ConfigService) {}

lookup: Reader<CityResponse> | null = null;
lru = new QuickLRU<string, Partial<CityResponse>>({
maxSize: this.configService.get<number>('caching.geolocationLruSize'),
});

onModuleDestroy() {
if (this.lookup) this.lookup = null;
}

/** Get the geolocation from an IP address */
async getLocation(ipAddress: string): Promise<Partial<CityResponse>> {
if (this.lru.has(ipAddress)) return this.lru.get(ipAddress);
const result = await this.getSafeLocation(ipAddress);
this.lru.set(ipAddress, result);
return result;
}

private async getSafeLocation(
ipAddress: string,
): Promise<Partial<CityResponse>> {
try {
return this.getUnsafeLocation(ipAddress);
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion src/templates/auth/approve-subnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Hi {{name}},

Someone (hopefully you) logged in to your account from a new location ({{ subnet }}), so you'll have to approve it.
Someone (hopefully you) logged in to your account from a new location ({{ locationName }}), so you'll have to approve it.

<a href="{{ link }}" class="btn btn-primary">Approve this login</a>

Expand Down

0 comments on commit d0bb693

Please sign in to comment.