Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New command: Revoke Sign-in Sessions. Closes #6514 #6544

Closed
Closed
Changes from 1 commit
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
Prev Previous commit
Next Next commit
New command: Revoke Sign-in Sessions
MartinM85 committed Jan 4, 2025
commit ff5e7ea893fc7d47987c2941f2f69d8d2c5edc9d
8 changes: 4 additions & 4 deletions docs/docs/cmd/entra/user/user-session-revoke.mdx
Original file line number Diff line number Diff line change
@@ -12,11 +12,11 @@ m365 entra user session revoke [options]

## Options
```md definition-list
`-i, --id [id]`
: The id of the user. Specify either `id` or `userName`, but not both.
`-i, --userId [userId]`
: The id of the user. Specify either `userId` or `userName`, but not both.

`-n, --userName [userName]`
: The user principal name of the user. Specify either `id` or `userName`, but not both.
: The user principal name of the user. Specify either `userId` or `userName`, but not both.

`-f, --force`
: Don't prompt for confirmation.
@@ -37,7 +37,7 @@ Only the user with Global Administrator role can revoke sign-in sessions of othe
Revoke sign-in sessions of a user specified by id

```sh
m365 entra user session revoke --id 4fb72b9b-d0b0-4a35-8bc1-83f9a6488c48
m365 entra user session revoke --userId 4fb72b9b-d0b0-4a35-8bc1-83f9a6488c48
```

Revoke sign-in sessions of a user specified by its UPN
22 changes: 11 additions & 11 deletions src/m365/entra/commands/user/user-session-revoke.spec.ts
Original file line number Diff line number Diff line change
@@ -77,9 +77,9 @@ describe(commands.USER_SESSION_REVOKE, () => {
assert.notStrictEqual(command.description, null);
});

it('fails validation if id is not a valid GUID', () => {
it('fails validation if userId is not a valid GUID', () => {
const actual = commandOptionsSchema.safeParse({
id: 'foo'
userId: 'foo'
});
assert.notStrictEqual(actual.success, true);
});
@@ -91,33 +91,33 @@ describe(commands.USER_SESSION_REVOKE, () => {
assert.notStrictEqual(actual.success, true);
});

it('fails validation if both id and userName are provided', () => {
it('fails validation if both userId and userName are provided', () => {
const actual = commandOptionsSchema.safeParse({
id: userId,
userId: userId,
userName: userName
});
assert.notStrictEqual(actual.success, true);
});

it('fails validation if neither id nor userName is provided', () => {
it('fails validation if neither userId nor userName is provided', () => {
const actual = commandOptionsSchema.safeParse({});
assert.notStrictEqual(actual.success, true);
});

it('prompts before revoking all sign-in sessions when confirm option not passed', async () => {
await command.action(logger, { options: { id: userId } });
await command.action(logger, { options: { userId: userId } });

assert(promptIssued);
});

it('aborts revoking all sign-in sessions when prompt not confirmed', async () => {
const deleteSpy = sinon.stub(request, 'delete').resolves();

await command.action(logger, { options: { id: userId } });
await command.action(logger, { options: { userId: userId } });
assert(deleteSpy.notCalled);
});

it('revokes all sign-in sessions for a user specified by id without prompting for confirmation', async () => {
it('revokes all sign-in sessions for a user specified by userId without prompting for confirmation', async () => {
const postRequestStub = sinon.stub(request, 'post').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/users/${userId}/revokeSignInSessions`) {
return;
@@ -126,7 +126,7 @@ describe(commands.USER_SESSION_REVOKE, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { id: userId, force: true, verbose: true } });
await command.action(logger, { options: { userId: userId, force: true, verbose: true } });
assert(postRequestStub.called);
});

@@ -159,7 +159,7 @@ describe(commands.USER_SESSION_REVOKE, () => {
assert(postRequestStub.called);
});

it('handles error when user specified by id was not found', async () => {
it('handles error when user specified by userId was not found', async () => {
sinon.stub(request, 'post').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/users/${userId}/revokeSignInSessions`) {
throw {
@@ -177,7 +177,7 @@ describe(commands.USER_SESSION_REVOKE, () => {
sinon.stub(cli, 'promptForConfirmation').resolves(true);

await assert.rejects(
command.action(logger, { options: { id: userId } }),
command.action(logger, { options: { userId: userId } }),
new CommandError(`Resource '${userId}' does not exist or one of its queried reference-property objects are not present.`)
);
});
24 changes: 12 additions & 12 deletions src/m365/entra/commands/user/user-session-revoke.ts
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import { cli } from '../../../../cli/cli.js';

const options = globalOptionsZod
.extend({
id: zod.alias('i', z.string().optional()),
userId: zod.alias('i', z.string().optional()),
userName: zod.alias('n', z.string().optional()),
force: zod.alias('f', z.boolean().optional())
})
@@ -35,25 +35,25 @@ class EntraUserSessionRevokeCommand extends GraphCommand {
}
public getRefinedSchema(schema: typeof options): z.ZodEffects<any> | undefined {
return schema
.refine(options => !options.id !== !options.userName, {
message: 'Specify either id or userName, but not both'
.refine(options => !options.userId !== !options.userName, {
message: 'Specify either userId or userName, but not both'
})
.refine(options => options.id || options.userName, {
message: 'Specify either id or userName'
.refine(options => options.userId || options.userName, {
message: 'Specify either userId or userName'
})
.refine(options => (!options.id && !options.userName) || options.userName || (options.id && validation.isValidGuid(options.id)), options => ({
message: `The '${options.id}' must be a valid GUID`,
path: ['id']
.refine(options => (!options.userId && !options.userName) || options.userName || (options.userId && validation.isValidGuid(options.userId)), options => ({
message: `The '${options.userId}' must be a valid GUID`,
path: ['userId']
}))
.refine(options => (!options.id && !options.userName) || options.id || (options.userName && validation.isValidUserPrincipalName(options.userName)), options => ({
.refine(options => (!options.userId && !options.userName) || options.userId || (options.userName && validation.isValidUserPrincipalName(options.userName)), options => ({
message: `The '${options.userName}' must be a valid UPN`,
path: ['id']
path: ['userId']
}));
}
public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
const revokeUserSessions = async (): Promise<void> => {
try {
let userIdOrPrincipalName = args.options.id;
let userIdOrPrincipalName = args.options.userId;

if (args.options.userName) {
// single user can be retrieved also by user principal name
@@ -87,7 +87,7 @@ class EntraUserSessionRevokeCommand extends GraphCommand {
await revokeUserSessions();
}
else {
const result = await cli.promptForConfirmation({ message: `Are you sure you want to invalidate all the refresh tokens issued to applications for a user '${args.options.id || args.options.userName}'?` });
const result = await cli.promptForConfirmation({ message: `Are you sure you want to invalidate all the refresh tokens issued to applications for a user '${args.options.userId || args.options.userName}'?` });

if (result) {
await revokeUserSessions();