mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Added support for fetching device details when creating session
This commit is contained in:
parent
29d1026606
commit
5ee2f91557
3 changed files with 84 additions and 4 deletions
|
@ -2,6 +2,10 @@ const {
|
||||||
BadRequestError
|
BadRequestError
|
||||||
} = require('@tryghost/errors');
|
} = require('@tryghost/errors');
|
||||||
const emailTemplate = require('../lib/emails/signin');
|
const emailTemplate = require('../lib/emails/signin');
|
||||||
|
const UAParser = require('ua-parser-js');
|
||||||
|
const got = require('got');
|
||||||
|
const IPV4_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||||
|
const IPV6_REGEX = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/i;
|
||||||
|
|
||||||
const {totp} = require('otplib');
|
const {totp} = require('otplib');
|
||||||
totp.options = {
|
totp.options = {
|
||||||
|
@ -143,6 +147,75 @@ module.exports = function createSessionService({
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatTime = new Intl.DateTimeFormat('en-GB', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZone: 'UTC',
|
||||||
|
timeZoneName: 'short'
|
||||||
|
}).format;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a readable location string from an IP address.
|
||||||
|
* @param {string} ip - The IP address to look up.
|
||||||
|
* @returns {Promise<string>} - A readable location string or 'Unknown Location'.
|
||||||
|
*/
|
||||||
|
async function getGeolocationFromIP(ip) {
|
||||||
|
ip = '212.19.89.120';
|
||||||
|
if (!ip || (!IPV4_REGEX.test(ip) && !IPV6_REGEX.test(ip))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gotOpts = {
|
||||||
|
timeout: 500
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV?.startsWith('test')) {
|
||||||
|
gotOpts.retry = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const geojsUrl = `https://get.geojs.io/v1/ip/geo/${encodeURIComponent(ip)}.json`;
|
||||||
|
const response = await got(geojsUrl, gotOpts).json();
|
||||||
|
|
||||||
|
const {
|
||||||
|
city = '',
|
||||||
|
region = '',
|
||||||
|
country = ''
|
||||||
|
} = response || {};
|
||||||
|
|
||||||
|
const locationParts = [];
|
||||||
|
|
||||||
|
if (city) {
|
||||||
|
locationParts.push(city);
|
||||||
|
}
|
||||||
|
if (region) {
|
||||||
|
locationParts.push(region);
|
||||||
|
}
|
||||||
|
if (country) {
|
||||||
|
locationParts.push(country);
|
||||||
|
}
|
||||||
|
|
||||||
|
return locationParts.join(', ').trim() || 'Unknown Location';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDeviceDetails(userAgent, ip) {
|
||||||
|
const parser = new UAParser();
|
||||||
|
parser.setUA(userAgent);
|
||||||
|
const result = parser.getResult();
|
||||||
|
const deviceParts = [
|
||||||
|
result.browser?.name || '',
|
||||||
|
result.os?.name || ''
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
return {
|
||||||
|
device: deviceParts.join(', '),
|
||||||
|
location: await getGeolocationFromIP(ip),
|
||||||
|
time: formatTime(new Date())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sendAuthCodeToUser
|
* sendAuthCodeToUser
|
||||||
*
|
*
|
||||||
|
@ -151,9 +224,8 @@ module.exports = function createSessionService({
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function sendAuthCodeToUser(req, res) {
|
async function sendAuthCodeToUser(req, res) {
|
||||||
const token = await generateAuthCodeForUser(req, res);
|
|
||||||
|
|
||||||
const session = await getSession(req, res);
|
const session = await getSession(req, res);
|
||||||
|
const token = await generateAuthCodeForUser(req, res);
|
||||||
const user = await findUserById({id: session.user_id});
|
const user = await findUserById({id: session.user_id});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
@ -172,7 +244,8 @@ module.exports = function createSessionService({
|
||||||
email: recipient,
|
email: recipient,
|
||||||
siteDomain: siteDomain,
|
siteDomain: siteDomain,
|
||||||
siteUrl: siteUrl,
|
siteUrl: siteUrl,
|
||||||
token
|
token: token,
|
||||||
|
deviceDetails: await getDeviceDetails(session.user_agent, session.ip)
|
||||||
});
|
});
|
||||||
|
|
||||||
await mailer.send({
|
await mailer.send({
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tryghost/errors": "1.3.5",
|
"@tryghost/errors": "1.3.5",
|
||||||
"otplib" : "12.0.1"
|
"otplib" : "12.0.1",
|
||||||
|
"ua-parser-js": "1.0.39",
|
||||||
|
"got": "11.8.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30491,6 +30491,11 @@ typescript@5.6.2, typescript@^5.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0"
|
||||||
integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==
|
integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==
|
||||||
|
|
||||||
|
ua-parser-js@1.0.39:
|
||||||
|
version "1.0.39"
|
||||||
|
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.39.tgz#bfc07f361549bf249bd8f4589a4cccec18fd2018"
|
||||||
|
integrity sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==
|
||||||
|
|
||||||
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
|
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
|
||||||
|
|
Loading…
Add table
Reference in a new issue