307 lines
9.3 KiB
TypeScript
307 lines
9.3 KiB
TypeScript
/**
|
|
* Cognito Identity Provider integration tests.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import {
|
|
CognitoIdentityProviderClient,
|
|
CreateUserPoolCommand,
|
|
CreateUserPoolClientCommand,
|
|
ExplicitAuthFlowsType,
|
|
AdminCreateUserCommand,
|
|
AdminSetUserPasswordCommand,
|
|
InitiateAuthCommand,
|
|
AdminGetUserCommand,
|
|
ListUsersCommand,
|
|
DeleteUserPoolCommand,
|
|
SignUpCommand,
|
|
ConfirmSignUpCommand,
|
|
CreateGroupCommand,
|
|
GetGroupCommand,
|
|
ListGroupsCommand,
|
|
DeleteGroupCommand,
|
|
AdminAddUserToGroupCommand,
|
|
AdminRemoveUserFromGroupCommand,
|
|
AdminListGroupsForUserCommand,
|
|
SetUserPoolMfaConfigCommand,
|
|
GetUserPoolMfaConfigCommand,
|
|
} from '@aws-sdk/client-cognito-identity-provider';
|
|
import { makeClient, uniqueName, ENDPOINT, fetchLatestSesVerificationCode } from './setup';
|
|
|
|
describe('Cognito', () => {
|
|
let cognito: CognitoIdentityProviderClient;
|
|
let poolId: string;
|
|
let clientId: string;
|
|
|
|
beforeAll(() => {
|
|
cognito = makeClient(CognitoIdentityProviderClient);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
try {
|
|
if (poolId) {
|
|
await cognito.send(new DeleteUserPoolCommand({ UserPoolId: poolId }));
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
});
|
|
|
|
it('should create user pool', async () => {
|
|
const response = await cognito.send(
|
|
new CreateUserPoolCommand({
|
|
PoolName: `test-pool-${uniqueName()}`,
|
|
AutoVerifiedAttributes: ['email'],
|
|
})
|
|
);
|
|
poolId = response.UserPool!.Id!;
|
|
expect(poolId).toBeTruthy();
|
|
});
|
|
|
|
it('should create user pool client', async () => {
|
|
const response = await cognito.send(
|
|
new CreateUserPoolClientCommand({
|
|
UserPoolId: poolId,
|
|
ClientName: `test-client-${uniqueName()}`,
|
|
ExplicitAuthFlows: [ExplicitAuthFlowsType.ALLOW_USER_PASSWORD_AUTH],
|
|
})
|
|
);
|
|
clientId = response.UserPoolClient!.ClientId!;
|
|
expect(clientId).toBeTruthy();
|
|
});
|
|
|
|
it('should admin create user', async () => {
|
|
const response = await cognito.send(
|
|
new AdminCreateUserCommand({
|
|
UserPoolId: poolId,
|
|
Username: 'testuser',
|
|
TemporaryPassword: 'Temp123!',
|
|
UserAttributes: [{ Name: 'email', Value: 'testuser@example.com' }],
|
|
})
|
|
);
|
|
expect(response.User?.Username).toBe('testuser');
|
|
});
|
|
|
|
it('should admin set user password', async () => {
|
|
await cognito.send(
|
|
new AdminSetUserPasswordCommand({
|
|
UserPoolId: poolId,
|
|
Username: 'testuser',
|
|
Password: 'Perm456!',
|
|
Permanent: true,
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should admin get user', async () => {
|
|
const response = await cognito.send(
|
|
new AdminGetUserCommand({ UserPoolId: poolId, Username: 'testuser' })
|
|
);
|
|
expect(response.UserStatus).toBe('CONFIRMED');
|
|
});
|
|
|
|
it('should list users', async () => {
|
|
const response = await cognito.send(new ListUsersCommand({ UserPoolId: poolId }));
|
|
expect(response.Users?.some((u) => u.Username === 'testuser')).toBe(true);
|
|
});
|
|
|
|
it('should initiate auth USER_PASSWORD_AUTH', async () => {
|
|
const response = await cognito.send(
|
|
new InitiateAuthCommand({
|
|
AuthFlow: 'USER_PASSWORD_AUTH',
|
|
AuthParameters: { USERNAME: 'testuser', PASSWORD: 'Perm456!' },
|
|
ClientId: clientId,
|
|
})
|
|
);
|
|
expect(response.AuthenticationResult?.AccessToken).toBeTruthy();
|
|
expect(response.AuthenticationResult?.IdToken).toBeTruthy();
|
|
});
|
|
|
|
it('should sign up and confirm user', async () => {
|
|
const email = 'testuser2@example.com';
|
|
await cognito.send(
|
|
new SignUpCommand({
|
|
ClientId: clientId,
|
|
Username: 'testuser2',
|
|
Password: 'Pass789!',
|
|
UserAttributes: [{ Name: 'email', Value: email }],
|
|
})
|
|
);
|
|
const confirmationCode = await fetchLatestSesVerificationCode(email);
|
|
await cognito.send(
|
|
new ConfirmSignUpCommand({
|
|
ClientId: clientId,
|
|
Username: 'testuser2',
|
|
ConfirmationCode: confirmationCode,
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should create group', async () => {
|
|
const response = await cognito.send(
|
|
new CreateGroupCommand({
|
|
UserPoolId: poolId,
|
|
GroupName: 'test-group',
|
|
Description: 'Test group',
|
|
Precedence: 1,
|
|
})
|
|
);
|
|
expect(response.Group?.GroupName).toBe('test-group');
|
|
});
|
|
|
|
it('should get group', async () => {
|
|
const response = await cognito.send(
|
|
new GetGroupCommand({ UserPoolId: poolId, GroupName: 'test-group' })
|
|
);
|
|
expect(response.Group?.GroupName).toBe('test-group');
|
|
expect(response.Group?.Description).toBe('Test group');
|
|
expect(response.Group?.Precedence).toBe(1);
|
|
});
|
|
|
|
it('should fail to create duplicate group', async () => {
|
|
await expect(
|
|
cognito.send(
|
|
new CreateGroupCommand({ UserPoolId: poolId, GroupName: 'test-group' })
|
|
)
|
|
).rejects.toThrow();
|
|
});
|
|
|
|
it('should list groups', async () => {
|
|
const response = await cognito.send(new ListGroupsCommand({ UserPoolId: poolId }));
|
|
expect(response.Groups?.some((g) => g.GroupName === 'test-group')).toBe(true);
|
|
});
|
|
|
|
it('should add user to group', async () => {
|
|
await cognito.send(
|
|
new AdminAddUserToGroupCommand({
|
|
UserPoolId: poolId,
|
|
GroupName: 'test-group',
|
|
Username: 'testuser',
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should list groups for user', async () => {
|
|
const response = await cognito.send(
|
|
new AdminListGroupsForUserCommand({ UserPoolId: poolId, Username: 'testuser' })
|
|
);
|
|
expect(response.Groups?.some((g) => g.GroupName === 'test-group')).toBe(true);
|
|
});
|
|
|
|
it('should return groups in JWT', async () => {
|
|
const response = await cognito.send(
|
|
new InitiateAuthCommand({
|
|
AuthFlow: 'USER_PASSWORD_AUTH',
|
|
AuthParameters: { USERNAME: 'testuser', PASSWORD: 'Perm456!' },
|
|
ClientId: clientId,
|
|
})
|
|
);
|
|
const token = response.AuthenticationResult?.AccessToken!;
|
|
const parts = token.split('.');
|
|
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
expect(Array.isArray(payload['cognito:groups'])).toBe(true);
|
|
expect(payload['cognito:groups']?.includes('test-group')).toBe(true);
|
|
});
|
|
|
|
it('should remove user from group', async () => {
|
|
await cognito.send(
|
|
new AdminRemoveUserFromGroupCommand({
|
|
UserPoolId: poolId,
|
|
GroupName: 'test-group',
|
|
Username: 'testuser',
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should verify user has no groups', async () => {
|
|
const response = await cognito.send(
|
|
new AdminListGroupsForUserCommand({ UserPoolId: poolId, Username: 'testuser' })
|
|
);
|
|
expect(response.Groups?.length).toBe(0);
|
|
});
|
|
|
|
it('should delete group', async () => {
|
|
await cognito.send(
|
|
new DeleteGroupCommand({ UserPoolId: poolId, GroupName: 'test-group' })
|
|
);
|
|
});
|
|
|
|
it('should fail to get deleted group', async () => {
|
|
await expect(
|
|
cognito.send(new GetGroupCommand({ UserPoolId: poolId, GroupName: 'test-group' }))
|
|
).rejects.toThrow();
|
|
});
|
|
|
|
it('should access JWKS endpoint', async () => {
|
|
const resp = await fetch(`${ENDPOINT}/${poolId}/.well-known/jwks.json`);
|
|
expect(resp.ok).toBe(true);
|
|
const json = await resp.json();
|
|
expect(Array.isArray(json.keys)).toBe(true);
|
|
expect(json.keys.length).toBeGreaterThan(0);
|
|
expect(json.keys[0].kty).toBe('RSA');
|
|
expect(json.keys[0].alg).toBe('RS256');
|
|
});
|
|
|
|
it('should delete user pool', async () => {
|
|
await cognito.send(new DeleteUserPoolCommand({ UserPoolId: poolId }));
|
|
poolId = '';
|
|
});
|
|
});
|
|
|
|
describe('Cognito MFA configuration', () => {
|
|
let cognito: CognitoIdentityProviderClient;
|
|
let poolId: string;
|
|
|
|
beforeAll(async () => {
|
|
cognito = makeClient(CognitoIdentityProviderClient);
|
|
const pool = await cognito.send(
|
|
new CreateUserPoolCommand({ PoolName: `mfa-${uniqueName()}` })
|
|
);
|
|
poolId = pool.UserPool!.Id!;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (poolId) {
|
|
await cognito
|
|
.send(new DeleteUserPoolCommand({ UserPoolId: poolId }))
|
|
.catch((error) => console.warn(`failed to delete MFA test pool ${poolId}`, error));
|
|
}
|
|
});
|
|
|
|
it('should report OFF and omit software-token config before it is set', async () => {
|
|
const config = await cognito.send(new GetUserPoolMfaConfigCommand({ UserPoolId: poolId }));
|
|
expect(config.MfaConfiguration).toBe('OFF');
|
|
// The live service returns only the factors that have been configured.
|
|
expect(config.SoftwareTokenMfaConfiguration).toBeUndefined();
|
|
});
|
|
|
|
it('should set and round-trip MFA configuration', async () => {
|
|
const set = await cognito.send(
|
|
new SetUserPoolMfaConfigCommand({
|
|
UserPoolId: poolId,
|
|
MfaConfiguration: 'OPTIONAL',
|
|
SoftwareTokenMfaConfiguration: { Enabled: true },
|
|
})
|
|
);
|
|
expect(set.MfaConfiguration).toBe('OPTIONAL');
|
|
expect(set.SoftwareTokenMfaConfiguration?.Enabled).toBe(true);
|
|
|
|
// What the Terraform provider reads back to decide whether
|
|
// mfa_configuration / software_token_mfa_configuration have drifted.
|
|
const got = await cognito.send(new GetUserPoolMfaConfigCommand({ UserPoolId: poolId }));
|
|
expect(got.MfaConfiguration).toBe('OPTIONAL');
|
|
expect(got.SoftwareTokenMfaConfiguration?.Enabled).toBe(true);
|
|
});
|
|
|
|
it('should reject an invalid MfaConfiguration', async () => {
|
|
await expect(
|
|
cognito.send(
|
|
new SetUserPoolMfaConfigCommand({
|
|
UserPoolId: poolId,
|
|
MfaConfiguration: 'SOMETIMES' as never,
|
|
})
|
|
)
|
|
).rejects.toThrow();
|
|
});
|
|
});
|