refactor(server): sessions (#8915)

* refactor: auth device => sessions

* chore: open api
This commit is contained in:
Jason Rasmussen 2024-04-19 06:47:29 -04:00 committed by GitHub
parent e72e41a7aa
commit 4478e524f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 967 additions and 825 deletions

View file

@ -19,11 +19,10 @@ import {
LOGIN_URL,
MOBILE_REDIRECT,
} from 'src/constants';
import { AccessCore, Permission } from 'src/cores/access.core';
import { AccessCore } from 'src/cores/access.core';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { UserCore } from 'src/cores/user.core';
import {
AuthDeviceResponseDto,
AuthDto,
ChangePasswordDto,
LoginCredentialDto,
@ -34,7 +33,6 @@ import {
OAuthConfigDto,
SignUpDto,
mapLoginResponse,
mapUserToken,
} from 'src/dtos/auth.dto';
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
import { SystemConfig } from 'src/entities/system-config.entity';
@ -44,9 +42,9 @@ import { IKeyRepository } from 'src/interfaces/api-key.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { ILibraryRepository } from 'src/interfaces/library.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
import { IUserTokenRepository } from 'src/interfaces/user-token.interface';
import { IUserRepository } from 'src/interfaces/user.interface';
import { HumanReadableSize } from 'src/utils/bytes';
@ -85,7 +83,7 @@ export class AuthService {
@Inject(ILibraryRepository) libraryRepository: ILibraryRepository,
@Inject(ILoggerRepository) private logger: ILoggerRepository,
@Inject(IUserRepository) private userRepository: IUserRepository,
@Inject(IUserTokenRepository) private userTokenRepository: IUserTokenRepository,
@Inject(ISessionRepository) private sessionRepository: ISessionRepository,
@Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository,
@Inject(IKeyRepository) private keyRepository: IKeyRepository,
) {
@ -120,8 +118,8 @@ export class AuthService {
}
async logout(auth: AuthDto, authType: AuthType): Promise<LogoutResponseDto> {
if (auth.userToken) {
await this.userTokenRepository.delete(auth.userToken.id);
if (auth.session) {
await this.sessionRepository.delete(auth.session.id);
}
return {
@ -164,8 +162,9 @@ export class AuthService {
async validate(headers: IncomingHttpHeaders, params: Record<string, string>): Promise<AuthDto> {
const shareKey = (headers['x-immich-share-key'] || params.key) as string;
const userToken = (headers['x-immich-user-token'] ||
params.userToken ||
const session = (headers['x-immich-user-token'] ||
headers['x-immich-session-token'] ||
params.sessionKey ||
this.getBearerToken(headers) ||
this.getCookieToken(headers)) as string;
const apiKey = (headers[IMMICH_API_KEY_HEADER] || params.apiKey) as string;
@ -174,8 +173,8 @@ export class AuthService {
return this.validateSharedLink(shareKey);
}
if (userToken) {
return this.validateUserToken(userToken);
if (session) {
return this.validateSession(session);
}
if (apiKey) {
@ -185,26 +184,6 @@ export class AuthService {
throw new UnauthorizedException('Authentication required');
}
async getDevices(auth: AuthDto): Promise<AuthDeviceResponseDto[]> {
const userTokens = await this.userTokenRepository.getAll(auth.user.id);
return userTokens.map((userToken) => mapUserToken(userToken, auth.userToken?.id));
}
async logoutDevice(auth: AuthDto, id: string): Promise<void> {
await this.access.requirePermission(auth, Permission.AUTH_DEVICE_DELETE, id);
await this.userTokenRepository.delete(id);
}
async logoutDevices(auth: AuthDto): Promise<void> {
const devices = await this.userTokenRepository.getAll(auth.user.id);
for (const device of devices) {
if (device.id === auth.userToken?.id) {
continue;
}
await this.userTokenRepository.delete(device.id);
}
}
getMobileRedirect(url: string) {
return `${MOBILE_REDIRECT}?${url.split('?')[1] || ''}`;
}
@ -408,19 +387,19 @@ export class AuthService {
return this.cryptoRepository.compareBcrypt(inputPassword, user.password);
}
private async validateUserToken(tokenValue: string): Promise<AuthDto> {
private async validateSession(tokenValue: string): Promise<AuthDto> {
const hashedToken = this.cryptoRepository.hashSha256(tokenValue);
let userToken = await this.userTokenRepository.getByToken(hashedToken);
let session = await this.sessionRepository.getByToken(hashedToken);
if (userToken?.user) {
if (session?.user) {
const now = DateTime.now();
const updatedAt = DateTime.fromJSDate(userToken.updatedAt);
const updatedAt = DateTime.fromJSDate(session.updatedAt);
const diff = now.diff(updatedAt, ['hours']);
if (diff.hours > 1) {
userToken = await this.userTokenRepository.save({ ...userToken, updatedAt: new Date() });
session = await this.sessionRepository.update({ id: session.id, updatedAt: new Date() });
}
return { user: userToken.user, userToken };
return { user: session.user, session: session };
}
throw new UnauthorizedException('Invalid user token');
@ -430,7 +409,7 @@ export class AuthService {
const key = this.cryptoRepository.newPassword(32);
const token = this.cryptoRepository.hashSha256(key);
await this.userTokenRepository.create({
await this.sessionRepository.create({
token,
user,
deviceOS: loginDetails.deviceOS,