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'], assetFiles: ['asset_file.id', 'asset_file.path', 'asset_file.type'],
authUser: ['user.id', 'user.name', 'user.email', 'user.isAdmin', 'user.quotaUsageInBytes', 'user.quotaSizeInBytes'], authUser: ['user.id', 'user.name', 'user.email', 'user.isAdmin', 'user.quotaUsageInBytes', 'user.quotaSizeInBytes'],
authApiKey: ['api_key.id', 'api_key.permissions'], 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: [ authSharedLink: [
'shared_link.id', 'shared_link.id',
'shared_link.userId', 'shared_link.userId',

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

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

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

@ -10,12 +10,28 @@ export const fromMaybeArray = <T>(param: T | T[]) => (Array.isArray(param) ? par
const getAppVersionFromUA = (ua: string) => const getAppVersionFromUA = (ua: string) =>
ua.match(/^Immich_(?:Android|iOS)_(?<appVersion>.+)$/)?.groups?.appVersion ?? null; 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) => { export const getUserAgentDetails = (headers: IncomingHttpHeaders) => {
const userAgent = UAParser(headers['user-agent']); const userAgent = UAParser(headers['user-agent']);
const appVersion = getAppVersionFromUA(headers['user-agent'] ?? ''); const appVersion = getAppVersionFromUA(headers['user-agent'] ?? '');
const isImmichApp = appVersion !== null || isImmichUserAgent(headers['user-agent'] ?? '');
return { 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) || '', deviceOS: userAgent.os.name || (headers['devicetype'] as string) || '',
appVersion, appVersion,
}; };

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