Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion api/src/unraid-api/cli/log.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,34 @@ import { LOG_LEVEL } from '@app/environment.js';
@Injectable()
export class LogService {
private logger = console;
private readonly isValidateTokenCommand: boolean;

constructor() {
// Check if running validate-token command
this.isValidateTokenCommand =
process.argv.includes('validate-token') ||
process.argv.includes('validate') ||
process.argv.includes('v') ||
(process.argv.includes('sso') &&
(process.argv.includes('validate-token') ||
process.argv.includes('validate') ||
process.argv.includes('v')));

// Suppress all console output for validate-token command
if (this.isValidateTokenCommand) {
const noop = () => {};
console.log = noop;
console.error = noop;
console.warn = noop;
console.info = noop;
console.debug = noop;
}
}

clear(): void {
this.logger.clear();
if (!this.isValidateTokenCommand) {
this.logger.clear();
}
}

shouldLog(level: LogLevel): boolean {
Expand Down
64 changes: 39 additions & 25 deletions api/src/unraid-api/cli/sso/validate-token.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,40 @@ export class ValidateTokenCommand extends CommandRunner {
this.JWKSOnline = createRemoteJWKSet(new URL(JWKS_REMOTE_LINK));
}

private createErrorAndExit = (errorMessage: string) => {
this.logger.error(
JSON.stringify({
error: errorMessage,
valid: false,
})
);
process.exit(1);
private logAndExit = (data: { error: string | null; valid: boolean; username?: string }) => {
const isError = data.error !== null;

// Restore the appropriate console method
if (isError) {
console.error = console.constructor.prototype.error;
} else {
console.log = console.constructor.prototype.log;
}

// Log the JSON response
const json = JSON.stringify(data);
if (isError) {
this.logger.error(json);
process.exit(1);
} else {
this.logger.info(json);
process.exit(0);
}
};

async run(passedParams: string[]): Promise<void> {
if (passedParams.length !== 1) {
this.createErrorAndExit('Please pass token argument only');
this.logAndExit({ error: 'Please pass token argument only', valid: false });
}

const token = passedParams[0];

if (typeof token !== 'string' || token.trim() === '') {
this.createErrorAndExit('Invalid token provided');
this.logAndExit({ error: 'Invalid token provided', valid: false });
}

if (!/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/.test(token)) {
this.createErrorAndExit('Token format is invalid');
this.logAndExit({ error: 'Token format is invalid', valid: false });
}

let caughtError: null | unknown = null;
Expand All @@ -66,20 +77,23 @@ export class ValidateTokenCommand extends CommandRunner {

if (caughtError) {
if (caughtError instanceof Error) {
this.createErrorAndExit(`Caught error validating jwt token: ${caughtError.message}`);
this.logAndExit({
error: `Caught error validating jwt token: ${caughtError.message}`,
valid: false,
});
} else {
this.createErrorAndExit('Caught unknown error validating jwt token');
this.logAndExit({ error: 'Caught unknown error validating jwt token', valid: false });
}
}

if (tokenPayload === null) {
this.createErrorAndExit('No data in JWT to use for user validation');
this.logAndExit({ error: 'No data in JWT to use for user validation', valid: false });
}

const username = tokenPayload?.sub;

if (!username) {
return this.createErrorAndExit('No ID found in token');
return this.logAndExit({ error: 'No ID found in token', valid: false });
}
const client = await this.internalClient.getClient();

Expand All @@ -89,25 +103,25 @@ export class ValidateTokenCommand extends CommandRunner {
query: SSO_USERS_QUERY,
});
} catch (error) {
this.createErrorAndExit('Failed to query SSO users');
this.logAndExit({ error: 'Failed to query SSO users', valid: false });
}

if (result.errors && result.errors.length > 0) {
this.createErrorAndExit('Failed to retrieve SSO configuration');
if (result!.errors && result!.errors.length > 0) {
this.logAndExit({ error: 'Failed to retrieve SSO configuration', valid: false });
}

const ssoUsers = result.data?.settings?.api?.ssoSubIds || [];
const ssoUsers = result!.data?.settings?.api?.ssoSubIds || [];

if (ssoUsers.length === 0) {
this.createErrorAndExit(
'No local user token set to compare to - please set any valid SSO IDs you would like to sign in with'
);
this.logAndExit({
error: 'No local user token set to compare to - please set any valid SSO IDs you would like to sign in with',
valid: false,
});
}
if (ssoUsers.includes(username)) {
this.logger.info(JSON.stringify({ error: null, valid: true, username }));
process.exit(0);
this.logAndExit({ error: null, valid: true, username });
} else {
this.createErrorAndExit('Username on token does not match');
this.logAndExit({ error: 'Username on token does not match', valid: false });
}
}
}