Skip to content

Commit 0d01091

Browse files
committed
feat(CORE-122): Guardar historial de login por usuario, correccion ip
1 parent da96d2f commit 0d01091

3 files changed

Lines changed: 161 additions & 0 deletions

File tree

auth/routes/routes.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { checkPassword } from '../ldap.controller';
77
import { AuthUsers } from '../schemas/authUsers';
88
import { Organizacion } from './../../core/tm/schemas/organizacion';
99
import { Auth } from './../auth.class';
10+
import { logUsuarioIngreso } from '../usuarioIngresos';
1011

1112

1213
const sha1Hash = require('sha1');
@@ -96,6 +97,11 @@ router.post('/v2/organizaciones', Auth.authenticate(), async (req, res, next) =>
9697
const dto = await generateTokenPayload(username, orgId, account_id);
9798
updateOrganizacion(usuario, orgId);
9899
if (dto) {
100+
try {
101+
await logUsuarioIngreso(req, dto.payload.usuario, dto.payload.organizacion);
102+
} catch (logErr) {
103+
console.error('Error logging usuario ingreso:', logErr);
104+
}
99105
return res.send({
100106
token: dto.token
101107
});
@@ -118,6 +124,11 @@ router.post('/organizaciones', Auth.authenticate(), async (req, res, next) => {
118124
const oldToken: string = String(req.headers.authorization).substring(4);
119125
const nuevosPermisos = user.organizaciones.find(item => String(item._id) === String(org._id));
120126
const refreshToken = Auth.refreshToken(oldToken, user, [...user.permisosGlobales, ...nuevosPermisos.permisos], org);
127+
try {
128+
await logUsuarioIngreso(req, { id: user._id, usuario: user.usuario }, org);
129+
} catch (logErr) {
130+
console.error('Error logging usuario ingreso in legacy route:', logErr);
131+
}
121132
return res.send({
122133
token: refreshToken
123134
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { AuditPlugin } from '@andes/mongoose-plugin-audit';
2+
import { Document, Model, model, Schema, SchemaTypes, Types } from 'mongoose';
3+
4+
export const UsuarioIngresoSchema = new Schema({
5+
usuario: {
6+
id: { type: SchemaTypes.ObjectId, required: true },
7+
usuario: { type: String, required: true }
8+
},
9+
start: { type: Date, required: true },
10+
cantidad: { type: Number, default: 0 },
11+
bucketNumber: { type: Number, default: 0 },
12+
ingresos: [{
13+
fecha: { type: Date, required: true },
14+
organizacion: {
15+
id: { type: SchemaTypes.ObjectId, required: true },
16+
nombre: { type: String, required: true }
17+
},
18+
device: {
19+
ip: String,
20+
tipo: String,
21+
os: String
22+
}
23+
}]
24+
});
25+
26+
export interface IUsuarioIngresos extends Document {
27+
usuario: {
28+
id: Types.ObjectId;
29+
usuario: string;
30+
};
31+
start: Date;
32+
cantidad: number;
33+
bucketNumber: number;
34+
ingresos: [{
35+
fecha: Date;
36+
organizacion: {
37+
id: Types.ObjectId;
38+
nombre: string;
39+
};
40+
device: {
41+
ip: string;
42+
tipo: string;
43+
os: string;
44+
};
45+
}];
46+
}
47+
48+
UsuarioIngresoSchema.index({ 'usuario.id': 1, start: -1, bucketNumber: 1 }, { unique: true });
49+
UsuarioIngresoSchema.plugin(AuditPlugin);
50+
51+
export const UsuarioIngreso: Model<IUsuarioIngresos> = model<IUsuarioIngresos>('usuarioIngreso', UsuarioIngresoSchema, 'usuarioIngresos');

auth/usuarioIngresos.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { UsuarioIngreso } from './schemas/usuarioIngresos.schema';
2+
import * as moment from 'moment';
3+
4+
function parseUserAgent(uaString: string): { tipo: string; os: string } {
5+
if (!uaString) {
6+
return { tipo: 'Unknown', os: 'Unknown' };
7+
}
8+
const ua = uaString.toLowerCase();
9+
10+
let tipo = 'Desktop';
11+
if (/mobile|android.*mobile|iphone|ipod|blackberry|windows phone/i.test(uaString)) {
12+
tipo = 'Mobile';
13+
} else if (/tablet|ipad|android(?!.*mobile)/i.test(uaString)) {
14+
tipo = 'Tablet';
15+
}
16+
17+
let os = 'Unknown';
18+
if (/windows nt 10/i.test(ua)) { os = 'Windows 10'; }
19+
else if (/windows nt 6\.3/i.test(ua)) { os = 'Windows 8.1'; }
20+
else if (/windows nt 6\.2/i.test(ua)) { os = 'Windows 8'; }
21+
else if (/windows nt 6\.1/i.test(ua)) { os = 'Windows 7'; }
22+
else if (/windows/i.test(ua)) { os = 'Windows'; }
23+
else if (/android/i.test(ua)) {
24+
const match = ua.match(/android\s([\d.]+)/);
25+
os = match ? `Android ${match[1]}` : 'Android';
26+
} else if (/iphone os|ipad/i.test(ua)) {
27+
const match = ua.match(/os ([\d_]+)/);
28+
os = match ? `iOS ${match[1].replace(/_/g, '.')}` : 'iOS';
29+
} else if (/mac os x/i.test(ua)) { os = 'macOS'; }
30+
else if (/linux/i.test(ua)) { os = 'Linux'; }
31+
32+
return { tipo, os };
33+
}
34+
35+
export async function logUsuarioIngreso(req, user, organizacion) {
36+
let bucketNumber = 0;
37+
let retry = true;
38+
while (retry) {
39+
try {
40+
await execLogIngreso(req, user, organizacion, bucketNumber);
41+
retry = false;
42+
} catch (err) {
43+
if (err.code === 17419 || err.code === 11000) {
44+
bucketNumber++;
45+
} else {
46+
retry = false;
47+
throw err;
48+
}
49+
}
50+
}
51+
}
52+
53+
async function execLogIngreso(req, user, organizacion, bucketNumber) {
54+
const now = new Date();
55+
const start = moment(now).startOf('quarter').toDate();
56+
57+
const uaString = req.headers['user-agent'] || '';
58+
const { tipo: deviceType, os } = parseUserAgent(uaString);
59+
const forwarded = req.headers['x-forwarded-for'];
60+
const rawIp = (typeof forwarded === 'string' ? forwarded.split(',')[0].trim() : null)
61+
|| req.ip
62+
|| req.connection?.remoteAddress
63+
|| '';
64+
const ip = rawIp.startsWith('::ffff:') ? rawIp.substring(7) : rawIp;
65+
66+
return UsuarioIngreso.update(
67+
{
68+
'usuario.id': user.id || user._id,
69+
start,
70+
bucketNumber
71+
},
72+
{
73+
$inc: { cantidad: 1 },
74+
$setOnInsert: {
75+
usuario: {
76+
id: user.id || user._id,
77+
usuario: user.usuario || user.username
78+
},
79+
start,
80+
bucketNumber
81+
},
82+
$push: {
83+
ingresos: {
84+
fecha: now,
85+
organizacion: {
86+
id: organizacion.id || organizacion._id,
87+
nombre: organizacion.nombre
88+
},
89+
device: {
90+
ip,
91+
tipo: deviceType,
92+
os
93+
}
94+
}
95+
}
96+
},
97+
{ upsert: true }
98+
);
99+
}

0 commit comments

Comments
 (0)