pull/23425/merge
aviv926 2025-12-10 16:58:59 +07:00 committed by GitHub
commit 3bced855ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 51 additions and 11 deletions

@ -344,7 +344,14 @@ export const columns = {
assetFiles: ['asset_file.id', 'asset_file.path', 'asset_file.type'],
authUser: ['user.id', 'user.name', 'user.email', 'user.isAdmin', 'user.quotaUsageInBytes', 'user.quotaSizeInBytes'],
authApiKey: ['api_key.id', 'api_key.permissions'],
authSession: ['session.id', 'session.updatedAt', 'session.pinExpiresAt', 'session.appVersion'],
authSession: [
'session.id',
'session.updatedAt',
'session.pinExpiresAt',
'session.appVersion',
'session.deviceOS',
'session.deviceType',
],
authSharedLink: [
'shared_link.id',
'shared_link.userId',

@ -24,6 +24,8 @@ select
"session"."updatedAt",
"session"."pinExpiresAt",
"session"."appVersion",
"session"."deviceOS",
"session"."deviceType",
(
select
to_json(obj)

@ -269,6 +269,8 @@ describe(AuthService.name, () => {
user: factory.authUser(),
pinExpiresAt: null,
appVersion: null,
deviceOS: '',
deviceType: '',
};
mocks.session.getByToken.mockResolvedValue(sessionWithToken);
@ -435,6 +437,8 @@ describe(AuthService.name, () => {
user: factory.authUser(),
pinExpiresAt: null,
appVersion: null,
deviceOS: '',
deviceType: '',
};
mocks.session.getByToken.mockResolvedValue(sessionWithToken);
@ -463,6 +467,8 @@ describe(AuthService.name, () => {
isPendingSyncReset: false,
pinExpiresAt: null,
appVersion: null,
deviceOS: '',
deviceType: '',
};
mocks.session.getByToken.mockResolvedValue(sessionWithToken);
@ -485,6 +491,8 @@ describe(AuthService.name, () => {
isPendingSyncReset: false,
pinExpiresAt: null,
appVersion: null,
deviceOS: '',
deviceType: '',
};
mocks.session.getByToken.mockResolvedValue(sessionWithToken);

@ -480,13 +480,21 @@ export class AuthService extends BaseService {
const updatedAt = DateTime.fromJSDate(session.updatedAt);
const diff = now.diff(updatedAt, ['hours']);
if (diff.hours > 1 || appVersion != session.appVersion) {
await this.sessionRepository.update(session.id, {
id: session.id,
const updates: Record<string, any> = {
updatedAt: new Date(),
appVersion,
deviceOS,
deviceType,
});
};
if (appVersion && appVersion !== session.appVersion) {
updates.appVersion = appVersion;
}
if (deviceOS && deviceOS !== session.deviceOS) {
updates.deviceOS = deviceOS;
}
if (deviceType && deviceType !== session.deviceType) {
updates.deviceType = deviceType;
}
await this.sessionRepository.update(session.id, updates);
}
// Pin check

@ -10,12 +10,28 @@ export const fromMaybeArray = <T>(param: T | T[]) => (Array.isArray(param) ? par
const getAppVersionFromUA = (ua: string) =>
ua.match(/^Immich_(?:Android|iOS)_(?<appVersion>.+)$/)?.groups?.appVersion ?? null;
const isImmichUserAgent = (ua: string) => {
const immichPatterns = [
/^Mobile$/,
/^Dart\//,
/^immich_mobile/,
/^AppleCoreMedia/,
/^Dalvik\//,
/^Immich_(?:Android|iOS)_/,
];
return immichPatterns.some((pattern) => pattern.test(ua));
};
export const getUserAgentDetails = (headers: IncomingHttpHeaders) => {
const userAgent = UAParser(headers['user-agent']);
const appVersion = getAppVersionFromUA(headers['user-agent'] ?? '');
const isImmichApp = appVersion !== null || isImmichUserAgent(headers['user-agent'] ?? '');
return {
deviceType: userAgent.browser.name || userAgent.device.type || (headers['devicemodel'] as string) || '',
deviceType: isImmichApp
? 'Immich app'
: userAgent.browser.name || userAgent.device.type || (headers['devicemodel'] as string) || '',
deviceOS: userAgent.os.name || (headers['devicetype'] as string) || '',
appVersion,
};

@ -57,9 +57,8 @@
<span class="text-sm">
{#if session.deviceType || session.deviceOS}
<span
>{session.deviceOS || $t('unknown')}{session.deviceType || $t('unknown')}{session.appVersion
? `(v${session.appVersion})`
: ''}</span
>{session.deviceOS || $t('unknown')}{session.deviceType || $t('unknown')}
{session.appVersion ? `(v${session.appVersion})` : ''}</span
>
{:else}
<span>{$t('unknown')}</span>