diff --git a/api/src/store/modules/config.ts b/api/src/store/modules/config.ts index c05991bc1..6fa919c26 100644 --- a/api/src/store/modules/config.ts +++ b/api/src/store/modules/config.ts @@ -211,6 +211,15 @@ export const config = createSlice({ setWanAccess(state, action: PayloadAction<'yes' | 'no'>) { state.remote.wanaccess = action.payload; }, + addSsoUser(state, action: PayloadAction) { + // First check if state already has ID, otherwise append it + if (state.remote.ssoSubIds.includes(action.payload)) { + return; + } + const stateAsArray = state.remote.ssoSubIds.split(','); + stateAsArray.push(action.payload); + state.remote.ssoSubIds = stateAsArray.join(','); + } }, extraReducers(builder) { builder.addCase(loadConfigFile.pending, (state) => { @@ -284,6 +293,7 @@ export const config = createSlice({ const { actions, reducer } = config; export const { + addSsoUser, updateUserConfig, updateAccessTokens, updateAllowedOrigins, diff --git a/api/src/unraid-api/cli/cli.module.ts b/api/src/unraid-api/cli/cli.module.ts index 71a34cdb3..9f60ea2c7 100644 --- a/api/src/unraid-api/cli/cli.module.ts +++ b/api/src/unraid-api/cli/cli.module.ts @@ -1,21 +1,27 @@ import { Module } from '@nestjs/common'; +import { InquirerService } from 'nest-commander'; + import { ConfigCommand } from '@app/unraid-api/cli/config.command'; import { KeyCommand } from '@app/unraid-api/cli/key.command'; import { LogService } from '@app/unraid-api/cli/log.service'; import { LogsCommand } from '@app/unraid-api/cli/logs.command'; import { ReportCommand } from '@app/unraid-api/cli/report.command'; import { RestartCommand } from '@app/unraid-api/cli/restart.command'; -import { SSOCommand } from '@app/unraid-api/cli/sso.command'; +import { AddSSOUserCommand } from '@app/unraid-api/cli/sso/add-sso-user.command'; +import { AddSSOUserQuestionSet } from '@app/unraid-api/cli/sso/add-sso-user.questions'; +import { SSOCommand } from '@app/unraid-api/cli/sso/sso.command'; +import { ValidateTokenCommand } from '@app/unraid-api/cli/sso/validate-token.command'; import { StartCommand } from '@app/unraid-api/cli/start.command'; import { StatusCommand } from '@app/unraid-api/cli/status.command'; import { StopCommand } from '@app/unraid-api/cli/stop.command'; import { SwitchEnvCommand } from '@app/unraid-api/cli/switch-env.command'; import { VersionCommand } from '@app/unraid-api/cli/version.command'; -import { ValidateTokenCommand } from '@app/unraid-api/cli/validate-token.command'; @Module({ providers: [ + AddSSOUserCommand, + AddSSOUserQuestionSet, LogService, StartCommand, StopCommand, diff --git a/api/src/unraid-api/cli/sso/add-sso-user.command.ts b/api/src/unraid-api/cli/sso/add-sso-user.command.ts new file mode 100644 index 000000000..a9640d1f4 --- /dev/null +++ b/api/src/unraid-api/cli/sso/add-sso-user.command.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@nestjs/common'; + +import { CommandRunner, InquirerService, Option, SubCommand } from 'nest-commander'; + +import { store } from '@app/store/index'; +import { addSsoUser, loadConfigFile } from '@app/store/modules/config'; +import { writeConfigSync } from '@app/store/sync/config-disk-sync'; +import { LogService } from '@app/unraid-api/cli/log.service'; +import { AddSSOUserQuestionSet } from '@app/unraid-api/cli/sso/add-sso-user.questions'; + +interface AddSSOUserCommandOptions { + disclaimer: string; + username: string; +} + +@Injectable() +@SubCommand({ + name: 'add-user', + aliases: ['add', 'a'], + description: 'Add a user for SSO', +}) +export class AddSSOUserCommand extends CommandRunner { + constructor( + private readonly logger: LogService, + private readonly inquirerService: InquirerService + ) { + super(); + } + + async run(_input: string[], options: AddSSOUserCommandOptions): Promise { + options = await this.inquirerService.prompt(AddSSOUserQuestionSet.name, options); + + if (options.disclaimer === 'y') { + await store.dispatch(loadConfigFile()); + store.dispatch(addSsoUser(options.username)); + writeConfigSync('flash'); + this.logger.info('User added ' + options.username); + } + } + + @Option({ + flags: '--username [username]', + description: 'Cognito Username', + }) + parseUsername(val: string) { + return val; + } + + @Option({ + flags: '--disclaimer [disclaimer]', + description: 'Disclaimer (y/n)', + }) + parseDisclaimer(val: string) { + return val; + } +} diff --git a/api/src/unraid-api/cli/sso/add-sso-user.questions.ts b/api/src/unraid-api/cli/sso/add-sso-user.questions.ts new file mode 100644 index 000000000..c9228349d --- /dev/null +++ b/api/src/unraid-api/cli/sso/add-sso-user.questions.ts @@ -0,0 +1,47 @@ +import { Question, QuestionSet } from 'nest-commander'; + + + + + +@QuestionSet({ name: 'add-user' }) +export class AddSSOUserQuestionSet { + static name = 'add-user'; + + @Question({ + message: 'Are you sure you wish to add a user for SSO - this will enable single sign on in Unraid and has certain security implications? (y/n)', + name: 'disclaimer', + validate(input) { + if (!input) { + return 'Please provide a response'; + } + if (!['y', 'n'].includes(input.toLowerCase())) { + return 'Please provide a valid response'; + } + if (input.toLowerCase() === 'n') { + process.exit(1); + } + return true; + }, + }) + parseDisclaimer(val: string) { + return val; + } + + @Question({ + message: 'What is the cognito username (NOT YOUR UNRAID USERNAME)? Find it in your Unraid Account at https://account.unraid.net', + name: 'username', + validate(input) { + if (!input) { + return 'Username is required'; + } + if (!/^[a-zA-Z0-9-]+$/.test(input)) { + return 'Username must be alphanumeric and can include dashes.'; + } + return true; + }, + }) + parseName(val: string) { + return val; + } +} \ No newline at end of file diff --git a/api/src/unraid-api/cli/sso.command.ts b/api/src/unraid-api/cli/sso/sso.command.ts similarity index 70% rename from api/src/unraid-api/cli/sso.command.ts rename to api/src/unraid-api/cli/sso/sso.command.ts index ce4593c03..220da3354 100644 --- a/api/src/unraid-api/cli/sso.command.ts +++ b/api/src/unraid-api/cli/sso/sso.command.ts @@ -3,13 +3,14 @@ import { Injectable } from '@nestjs/common'; import { Command, CommandRunner } from 'nest-commander'; import { LogService } from '@app/unraid-api/cli/log.service'; -import { ValidateTokenCommand } from '@app/unraid-api/cli/validate-token.command'; +import { ValidateTokenCommand } from '@app/unraid-api/cli/sso/validate-token.command'; +import { AddSSOUserCommand } from '@app/unraid-api/cli/sso/add-sso-user.command'; @Injectable() @Command({ name: 'sso', description: 'Main Command to Configure / Validate SSO Tokens', - subCommands: [ValidateTokenCommand], + subCommands: [ValidateTokenCommand, AddSSOUserCommand], }) export class SSOCommand extends CommandRunner { constructor(private readonly logger: LogService) { diff --git a/api/src/unraid-api/cli/validate-token.command.ts b/api/src/unraid-api/cli/sso/validate-token.command.ts similarity index 100% rename from api/src/unraid-api/cli/validate-token.command.ts rename to api/src/unraid-api/cli/sso/validate-token.command.ts