Skip to content

Commit b62f1b1

Browse files
committed
use separate LocalSessionLifecycleService to manage local session
validation
1 parent 1c7f3c2 commit b62f1b1

File tree

3 files changed

+44
-21
lines changed

3 files changed

+44
-21
lines changed

api/src/unraid-api/auth/auth.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { BASE_POLICY, CASBIN_MODEL } from '@app/unraid-api/auth/casbin/index.js'
1111
import { CookieService, SESSION_COOKIE_CONFIG } from '@app/unraid-api/auth/cookie.service.js';
1212
import { UserCookieStrategy } from '@app/unraid-api/auth/cookie.strategy.js';
1313
import { ServerHeaderStrategy } from '@app/unraid-api/auth/header.strategy.js';
14+
import { LocalSessionLifecycleService } from '@app/unraid-api/auth/local-session-lifecycle.service.js';
1415
import { LocalSessionService } from '@app/unraid-api/auth/local-session.service.js';
1516
import { LocalSessionStrategy } from '@app/unraid-api/auth/local-session.strategy.js';
1617
import { getRequest } from '@app/utils.js';
@@ -75,6 +76,7 @@ import { getRequest } from '@app/utils.js';
7576
UserCookieStrategy,
7677
CookieService,
7778
LocalSessionService,
79+
LocalSessionLifecycleService,
7880
AuthZModule,
7981
],
8082
})
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
2+
3+
import { LocalSessionService } from '@app/unraid-api/auth/local-session.service.js';
4+
5+
/**
6+
* Service for managing the lifecycle of the local session.
7+
*
8+
* Used for tying the local session's lifecycle to the API's life, rather
9+
* than the LocalSessionService's lifecycle, since it may also be used by
10+
* other applications, like the CLI.
11+
*
12+
* This service is only used in the API, and not in the CLI.
13+
*/
14+
@Injectable()
15+
export class LocalSessionLifecycleService implements OnModuleInit, OnModuleDestroy {
16+
constructor(private readonly localSessionService: LocalSessionService) {}
17+
18+
async onModuleInit() {
19+
await this.localSessionService.generateLocalSession();
20+
}
21+
22+
async onModuleDestroy() {
23+
await this.localSessionService.deleteLocalSession();
24+
}
25+
}

api/src/unraid-api/auth/local-session.service.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,39 @@
1-
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
1+
import { Injectable, Logger } from '@nestjs/common';
22
import { randomBytes, timingSafeEqual } from 'crypto';
3-
import { chmod, mkdir, readFile, writeFile } from 'fs/promises';
3+
import { chmod, mkdir, readFile, unlink, writeFile } from 'fs/promises';
44
import { dirname } from 'path';
55

66
/**
77
* Service that manages a local session file for internal CLI/system authentication.
88
* Creates a secure token on startup that can be used for local system operations.
99
*/
1010
@Injectable()
11-
export class LocalSessionService implements OnModuleInit {
11+
export class LocalSessionService {
1212
private readonly logger = new Logger(LocalSessionService.name);
1313
private sessionToken: string | null = null;
1414
private static readonly SESSION_FILE_PATH = '/var/run/unraid-api/local-session';
1515

16-
// NOTE: do NOT cleanup the session file upon eg. module/application shutdown.
17-
// That would invalidate the session after each cli invocation, which is incorrect.
18-
// Instead, rely on the startup logic to invalidate/overwrite any obsolete session.
19-
async onModuleInit() {
20-
try {
21-
await this.generateLocalSession();
22-
this.logger.verbose('Local session initialized');
23-
} catch (error) {
24-
this.logger.error('Failed to initialize local session:', error);
25-
}
26-
}
27-
2816
/**
2917
* Generate a secure local session token and write it to file
3018
*/
31-
private async generateLocalSession(): Promise<void> {
19+
async generateLocalSession(): Promise<void> {
3220
// Generate a cryptographically secure random token
3321
this.sessionToken = randomBytes(32).toString('hex');
3422

3523
try {
3624
// Ensure directory exists
37-
await mkdir(dirname(LocalSessionService.SESSION_FILE_PATH), { recursive: true });
25+
await mkdir(dirname(LocalSessionService.getSessionFilePath()), { recursive: true });
3826

3927
// Write token to file
40-
await writeFile(LocalSessionService.SESSION_FILE_PATH, this.sessionToken, {
28+
await writeFile(LocalSessionService.getSessionFilePath(), this.sessionToken, {
4129
encoding: 'utf-8',
4230
mode: 0o600, // Owner read/write only
4331
});
4432

4533
// Ensure proper permissions (redundant but explicit)
46-
await chmod(LocalSessionService.SESSION_FILE_PATH, 0o600);
34+
await chmod(LocalSessionService.getSessionFilePath(), 0o600);
4735

48-
this.logger.debug(`Local session written to ${LocalSessionService.SESSION_FILE_PATH}`);
36+
this.logger.debug(`Local session written to ${LocalSessionService.getSessionFilePath()}`);
4937
} catch (error) {
5038
this.logger.error(`Failed to write local session: ${error}`);
5139
throw error;
@@ -57,7 +45,7 @@ export class LocalSessionService implements OnModuleInit {
5745
*/
5846
public async getLocalSession(): Promise<string | null> {
5947
try {
60-
return await readFile(LocalSessionService.SESSION_FILE_PATH, 'utf-8');
48+
return await readFile(LocalSessionService.getSessionFilePath(), 'utf-8');
6149
} catch (error) {
6250
this.logger.warn(error, 'Local session file not found or not readable');
6351
return null;
@@ -77,6 +65,14 @@ export class LocalSessionService implements OnModuleInit {
7765
return timingSafeEqual(Buffer.from(token, 'utf-8'), Buffer.from(currentToken, 'utf-8'));
7866
}
7967

68+
public async deleteLocalSession(): Promise<void> {
69+
try {
70+
await unlink(LocalSessionService.getSessionFilePath());
71+
} catch (error) {
72+
this.logger.error(error, 'Error deleting local session file');
73+
}
74+
}
75+
8076
/**
8177
* Get the file path for the local session (useful for external readers)
8278
*/

0 commit comments

Comments
 (0)