Files
floci-ui/packages/api/src/adapter-aws/AwsUserPoolsAdapter.test.ts
T
2026-10-24 11:17:42 -04:00

473 lines
26 KiB
TypeScript

import {createHmac} from 'node:crypto'
import {describe, expect, test} from 'bun:test'
import {
AdminAddUserToGroupCommand,
AdminConfirmSignUpCommand,
AdminDisableUserCommand,
AdminListGroupsForUserCommand,
AdminRemoveUserFromGroupCommand,
AdminUserGlobalSignOutCommand,
CreateGroupCommand,
DeleteGroupCommand,
DescribeUserPoolClientCommand,
InitiateAuthCommand,
ListGroupsCommand,
AdminCreateUserCommand,
AdminDeleteUserCommand,
AdminGetUserCommand,
AdminSetUserPasswordCommand,
CreateUserPoolClientCommand,
CreateUserPoolCommand,
DeleteUserPoolCommand,
DescribeUserPoolCommand,
ListUserPoolClientsCommand,
ListUserPoolsCommand,
ListUsersCommand,
UpdateUserPoolClientCommand,
type CognitoIdentityProviderClient,
} from '@aws-sdk/client-cognito-identity-provider'
import {AwsUserPoolsAdapter} from './AwsUserPoolsAdapter'
import {ConflictError, ValidationError} from '../cloud-spi/errors'
type SendResult = Record<string, unknown>
function stubCognito(handler: (command: object) => SendResult | Promise<SendResult>) {
const sent: object[] = []
const client = {
async send(command: object) {
sent.push(command)
return handler(command)
},
} as unknown as CognitoIdentityProviderClient
return {client, sent}
}
function awsError(name: string): Error {
return Object.assign(new Error(name), {name, $metadata: {httpStatusCode: 400}})
}
const POOL_ID = 'us-east-1_8e65d229a'
const CREATED = new Date('2026-10-23T16:01:22.000Z')
const pool = {
Id: POOL_ID,
Name: 'orders-users',
Arn: `arn:aws:cognito-idp:us-east-1:000000000000:userpool/${POOL_ID}`,
Status: 'Enabled',
CreationDate: CREATED,
UsernameAttributes: ['email'],
EstimatedNumberOfUsers: 1,
MfaConfiguration: 'OFF',
DeletionProtection: 'INACTIVE',
Policies: {PasswordPolicy: {MinimumLength: 8, RequireUppercase: true, RequireLowercase: true, RequireNumbers: true, RequireSymbols: true}},
}
const alice = {
Username: 'aa36ecc1-ed8a-43b5-84e9-988398c17436',
Attributes: [
{Name: 'sub', Value: 'aa36ecc1-ed8a-43b5-84e9-988398c17436'},
{Name: 'email', Value: 'alice@example.com'},
{Name: 'email_verified', Value: 'true'},
],
UserCreateDate: CREATED,
UserLastModifiedDate: CREATED,
Enabled: true,
UserStatus: 'CONFIRMED',
}
function runtimeStub() {
return stubCognito((command) => {
if (command instanceof CreateUserPoolCommand) return {UserPool: pool}
if (command instanceof DescribeUserPoolCommand) return {UserPool: pool}
if (command instanceof ListUserPoolClientsCommand) return {UserPoolClients: [{ClientId: 'a8e5c7d38d3b4a1a97c9d822de', ClientName: 'orders-users-client', UserPoolId: POOL_ID}]}
if (command instanceof AdminCreateUserCommand) return {User: {...alice, UserStatus: 'FORCE_CHANGE_PASSWORD'}}
if (command instanceof AdminGetUserCommand) return {...alice, UserAttributes: alice.Attributes}
return {}
})
}
describe('AwsUserPoolsAdapter', () => {
test('lists every page of user pools and filters by name or id', async () => {
const {client, sent} = stubCognito((command) => {
if (!(command instanceof ListUserPoolsCommand)) return {}
return command.input.NextToken
? {UserPools: [{Id: 'us-east-1_second', Name: 'billing', Status: 'Enabled', CreationDate: CREATED}]}
: {UserPools: [{Id: POOL_ID, Name: 'orders-users', Status: 'Enabled', CreationDate: CREATED}], NextToken: 'page-2'}
})
const adapter = new AwsUserPoolsAdapter(client)
expect((await adapter.list()).map((resource) => resource.id)).toEqual([POOL_ID, 'us-east-1_second'])
expect(sent.filter((command) => command instanceof ListUserPoolsCommand)).toHaveLength(2)
expect((await adapter.list({search: 'BILL'})).map((resource) => resource.name)).toEqual(['billing'])
expect((await adapter.list())[0]).toMatchObject({type: 'user-pool', region: 'us-east-1', status: 'Enabled', createdAt: CREATED.toISOString()})
})
test('lists pools with the details the inspector shows', async () => {
const {client} = stubCognito((command) => {
if (command instanceof ListUserPoolsCommand) return {UserPools: [{Id: POOL_ID, Name: 'orders-users', Status: 'Enabled', CreationDate: CREATED}]}
if (command instanceof DescribeUserPoolCommand) return {UserPool: pool}
if (command instanceof ListUserPoolClientsCommand) return {UserPoolClients: [{ClientId: 'a8e5c7d38d3b4a1a97c9d822de', ClientName: 'orders-users-client'}]}
return {}
})
const [listed] = await new AwsUserPoolsAdapter(client).list()
expect(listed.metadata).toMatchObject({signInWith: 'email', appClients: [{clientId: 'a8e5c7d38d3b4a1a97c9d822de', clientName: 'orders-users-client'}]})
})
test('describes a pool with its sign-in method, password policy and app clients', async () => {
const resource = await new AwsUserPoolsAdapter(runtimeStub().client).get(POOL_ID)
expect(resource?.metadata).toEqual({
arn: pool.Arn,
signInWith: 'email',
passwordPolicy: '8+ characters with uppercase, lowercase, number, symbol',
estimatedNumberOfUsers: 1,
userCount: 0,
mfaConfiguration: 'OFF',
deletionProtection: 'INACTIVE',
appClients: [{clientId: 'a8e5c7d38d3b4a1a97c9d822de', clientName: 'orders-users-client'}],
lambdaTriggers: [],
})
})
test('lists the pool\'s Lambda triggers in sign-in order, with the trigger version when set', async () => {
const arn = (name: string) => `arn:aws:lambda:us-east-1:000000000000:function:${name}`
const {client} = stubCognito((command) => {
if (command instanceof DescribeUserPoolCommand) return {UserPool: {...pool, LambdaConfig: {
PreTokenGeneration: arn('claims'),
PreTokenGenerationConfig: {LambdaArn: arn('claims'), LambdaVersion: 'V2_0'},
PreSignUp: `${arn('signup')}:live`,
CustomEmailSender: {LambdaArn: arn('mailer'), LambdaVersion: 'V1_0'},
KMSKeyID: 'arn:aws:kms:us-east-1:000000000000:key/1',
}}}
return {}
})
const resource = await new AwsUserPoolsAdapter(client).get(POOL_ID)
expect(resource?.metadata.lambdaTriggers).toEqual([
{trigger: 'PreSignUp', label: 'Pre sign-up', functionArn: `${arn('signup')}:live`, functionName: 'signup', version: null},
{trigger: 'PreTokenGeneration', label: 'Pre token generation', functionArn: arn('claims'), functionName: 'claims', version: 'V2_0'},
{trigger: 'CustomEmailSender', label: 'Custom email sender', functionArn: arn('mailer'), functionName: 'mailer', version: 'V1_0'},
])
})
test('counts users itself, since the runtime leaves EstimatedNumberOfUsers at 0', async () => {
const {client} = stubCognito((command) => {
if (command instanceof DescribeUserPoolCommand) return {UserPool: {...pool, EstimatedNumberOfUsers: 0}}
if (command instanceof ListUsersCommand) return command.input.PaginationToken ? {Users: [alice]} : {Users: [alice, alice], PaginationToken: 'page-2'}
return {}
})
const resource = await new AwsUserPoolsAdapter(client).get(POOL_ID)
expect(resource?.metadata.userCount).toBe(3)
expect(resource?.metadata.estimatedNumberOfUsers).toBe(0)
})
test('summarises a relaxed password policy without character rules', async () => {
const {client} = stubCognito((command) => {
if (command instanceof DescribeUserPoolCommand) return {UserPool: {...pool, Policies: {PasswordPolicy: {MinimumLength: 8, RequireUppercase: false, RequireLowercase: false, RequireNumbers: false, RequireSymbols: false}}}}
return {}
})
expect((await new AwsUserPoolsAdapter(client).get(POOL_ID))?.metadata.passwordPolicy).toBe('8+ characters')
})
test('returns null for a pool the runtime does not know', async () => {
const {client} = stubCognito(() => { throw awsError('ResourceNotFoundException') })
expect(await new AwsUserPoolsAdapter(client).get('us-east-1_missing')).toBeNull()
})
test('creates an email pool with a public app client by default', async () => {
const {client, sent} = runtimeStub()
await new AwsUserPoolsAdapter(client).create({values: {name: 'orders-users'}})
const createPool = sent.find((command) => command instanceof CreateUserPoolCommand) as CreateUserPoolCommand
expect(createPool.input).toEqual({PoolName: 'orders-users', UsernameAttributes: ['email'], AutoVerifiedAttributes: ['email']})
const createClient = sent.find((command) => command instanceof CreateUserPoolClientCommand) as CreateUserPoolClientCommand
expect(createClient.input).toEqual({
UserPoolId: POOL_ID,
ClientName: 'orders-users-client',
GenerateSecret: false,
ExplicitAuthFlows: ['ALLOW_USER_PASSWORD_AUTH', 'ALLOW_USER_SRP_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH'],
})
})
test('creates a username pool with a relaxed password policy and no app client', async () => {
const {client, sent} = runtimeStub()
await new AwsUserPoolsAdapter(client).create({values: {name: 'testers', signInWith: 'username', passwordPolicy: 'relaxed', createAppClient: 'no'}})
const createPool = sent.find((command) => command instanceof CreateUserPoolCommand) as CreateUserPoolCommand
expect(createPool.input).toEqual({
PoolName: 'testers',
Policies: {PasswordPolicy: {MinimumLength: 8, RequireUppercase: false, RequireLowercase: false, RequireNumbers: false, RequireSymbols: false}},
})
expect(sent.some((command) => command instanceof CreateUserPoolClientCommand)).toBe(false)
})
test('removes the new pool when its app client cannot be created', async () => {
const {client, sent} = stubCognito((command) => {
if (command instanceof CreateUserPoolCommand) return {UserPool: pool}
if (command instanceof CreateUserPoolClientCommand) throw awsError('InvalidParameterException')
return {}
})
await expect(new AwsUserPoolsAdapter(client).create({values: {name: 'orders-users'}})).rejects.toThrow('InvalidParameterException')
expect((sent.at(-1) as DeleteUserPoolCommand).input).toEqual({UserPoolId: POOL_ID})
})
test('rejects invalid create input before calling the runtime', async () => {
const {client, sent} = runtimeStub()
const adapter = new AwsUserPoolsAdapter(client)
await expect(adapter.create({values: {}})).rejects.toBeInstanceOf(ValidationError)
await expect(adapter.create({values: {name: 'bad/name'}})).rejects.toBeInstanceOf(ValidationError)
await expect(adapter.create({values: {name: 'ok', signInWith: 'phone'}})).rejects.toBeInstanceOf(ValidationError)
expect(sent).toHaveLength(0)
})
test('deletes a pool by id', async () => {
const {client, sent} = runtimeStub()
await new AwsUserPoolsAdapter(client).delete(POOL_ID)
expect(sent[0]).toBeInstanceOf(DeleteUserPoolCommand)
expect((sent[0] as DeleteUserPoolCommand).input).toEqual({UserPoolId: POOL_ID})
})
})
describe('AwsUserPoolsAdapter users', () => {
test('lists users as items keyed by username with their email and status', async () => {
const {client, sent} = stubCognito(() => ({Users: [alice], PaginationToken: 'next-page'}))
const page = await new AwsUserPoolsAdapter(client).items.listItems(POOL_ID, {limit: 500, cursor: 'this-page'})
expect((sent[0] as ListUsersCommand).input).toEqual({UserPoolId: POOL_ID, Limit: 60, PaginationToken: 'this-page'})
expect(page.nextCursor).toBe('next-page')
expect(page.items).toEqual([{
id: alice.Username,
collectionId: null,
timestamp: CREATED.toISOString(),
body: {
username: alice.Username,
email: 'alice@example.com',
emailVerified: true,
status: 'CONFIRMED',
enabled: true,
lastModifiedAt: CREATED.toISOString(),
attributes: {sub: alice.Username, email: 'alice@example.com', email_verified: 'true'},
},
metadata: {},
}])
})
test('creates a user whose password is permanent so it can sign in at once', async () => {
const {client, sent} = runtimeStub()
const user = await new AwsUserPoolsAdapter(client).items.putItem!(POOL_ID, {email: 'alice@example.com', password: ' Passw0rd! '})
expect((sent[0] as AdminCreateUserCommand).input).toEqual({
UserPoolId: POOL_ID,
Username: 'alice@example.com',
UserAttributes: [{Name: 'email', Value: 'alice@example.com'}, {Name: 'email_verified', Value: 'true'}],
TemporaryPassword: ' Passw0rd! ',
MessageAction: 'SUPPRESS',
})
expect((sent[1] as AdminSetUserPasswordCommand).input).toEqual({UserPoolId: POOL_ID, Username: alice.Username, Password: ' Passw0rd! ', Permanent: true})
expect(user.body.status).toBe('CONFIRMED')
})
test('leaves the password temporary when asked', async () => {
const {client, sent} = runtimeStub()
await new AwsUserPoolsAdapter(client).items.putItem!(POOL_ID, {username: 'bob', password: 'Passw0rd!', permanent: false})
expect((sent[0] as AdminCreateUserCommand).input.Username).toBe('bob')
expect((sent[0] as AdminCreateUserCommand).input.UserAttributes).toEqual([])
expect(sent.some((command) => command instanceof AdminSetUserPasswordCommand)).toBe(false)
})
test('rejects a user without a name, password or valid email', async () => {
const {client, sent} = runtimeStub()
const users = new AwsUserPoolsAdapter(client).items
await expect(users.putItem!(POOL_ID, {password: 'Passw0rd!'})).rejects.toThrow('Provide a username or an email address')
await expect(users.putItem!(POOL_ID, {email: 'alice@example.com'})).rejects.toThrow('password is required')
await expect(users.putItem!(POOL_ID, {email: 'not-an-email', password: 'Passw0rd!'})).rejects.toThrow('email must be a valid email address')
await expect(users.putItem!(POOL_ID, {username: 'bob', password: 'Passw0rd!', permanent: 'yes'})).rejects.toThrow('permanent must be true or false')
expect(sent).toHaveLength(0)
})
test('returns null for a missing user and deletes by username', async () => {
const missing = stubCognito(() => { throw awsError('UserNotFoundException') })
expect(await new AwsUserPoolsAdapter(missing.client).items.getItem!(POOL_ID, 'nobody')).toBeNull()
const {client, sent} = runtimeStub()
await new AwsUserPoolsAdapter(client).items.deleteItem!(POOL_ID, alice.Username)
expect((sent[0] as AdminDeleteUserCommand).input).toEqual({UserPoolId: POOL_ID, Username: alice.Username})
})
})
function jwt(claims: Record<string, unknown>): string {
return ['{"alg":"RS256"}', JSON.stringify(claims), 'sig'].map((part) => Buffer.from(part).toString('base64url')).join('.')
}
describe('AwsUserPoolsAdapter user actions and groups', () => {
test('reads a user with the groups it belongs to', async () => {
const {client, sent} = stubCognito((command) => {
if (command instanceof AdminGetUserCommand) return {Username: alice.Username, UserAttributes: alice.Attributes, UserStatus: 'CONFIRMED', Enabled: true, UserCreateDate: CREATED}
if (command instanceof AdminListGroupsForUserCommand) return {Groups: [{GroupName: 'admins'}]}
return {}
})
const user = await new AwsUserPoolsAdapter(client).items.getItem!(POOL_ID, alice.Username)
expect(user?.body.groups).toEqual(['admins'])
expect((sent[1] as AdminListGroupsForUserCommand).input).toMatchObject({UserPoolId: POOL_ID, Username: alice.Username})
})
test('runs each user action as its Admin call', async () => {
const {client, sent} = stubCognito(() => ({}))
const adapter = new AwsUserPoolsAdapter(client)
await adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'disable'})
await adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'confirm'})
await adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'signOut'})
await adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'setPassword', password: ' Passw0rd! '})
expect(sent[0]).toBeInstanceOf(AdminDisableUserCommand)
expect(sent[1]).toBeInstanceOf(AdminConfirmSignUpCommand)
expect(sent[2]).toBeInstanceOf(AdminUserGlobalSignOutCommand)
expect((sent[3] as AdminSetUserPasswordCommand).input).toEqual({UserPoolId: POOL_ID, Username: 'bob', Password: ' Passw0rd! ', Permanent: true})
})
test('rejects unknown actions and reports a refused one as a conflict, never a 401', async () => {
const {client} = stubCognito(() => { throw awsError('NotAuthorizedException') })
const adapter = new AwsUserPoolsAdapter(client)
await expect(adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'explode'} as never)).rejects.toBeInstanceOf(ValidationError)
await expect(adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'setPassword'} as never)).rejects.toBeInstanceOf(ValidationError)
await expect(adapter.updateUserPoolUser(POOL_ID, 'bob', {action: 'confirm'})).rejects.toBeInstanceOf(ConflictError)
})
test('sets a user\'s groups by adding and removing only the difference', async () => {
const {client, sent} = stubCognito((command) => command instanceof AdminListGroupsForUserCommand ? {Groups: [{GroupName: 'staff'}, {GroupName: 'beta'}]} : {})
await new AwsUserPoolsAdapter(client).setUserPoolUserGroups(POOL_ID, 'bob', ['staff', 'admins'])
const changes = sent.slice(1)
expect(changes).toHaveLength(2)
expect(changes[0]).toBeInstanceOf(AdminAddUserToGroupCommand)
expect((changes[0] as AdminAddUserToGroupCommand).input.GroupName).toBe('admins')
expect(changes[1]).toBeInstanceOf(AdminRemoveUserFromGroupCommand)
expect((changes[1] as AdminRemoveUserFromGroupCommand).input.GroupName).toBe('beta')
})
test('lists, creates and deletes groups', async () => {
const {client, sent} = stubCognito((command) => {
if (command instanceof ListGroupsCommand) return {Groups: [{GroupName: 'admins', Description: 'Full access', Precedence: 0, CreationDate: CREATED}]}
if (command instanceof CreateGroupCommand) return {Group: {GroupName: command.input.GroupName, Precedence: command.input.Precedence, CreationDate: CREATED}}
return {}
})
const adapter = new AwsUserPoolsAdapter(client)
expect(await adapter.listUserPoolGroups(POOL_ID)).toEqual([{name: 'admins', description: 'Full access', precedence: 0, createdAt: CREATED.toISOString()}])
expect(await adapter.createUserPoolGroup(POOL_ID, {name: 'editors', precedence: 5})).toMatchObject({name: 'editors', precedence: 5})
await adapter.deleteUserPoolGroup(POOL_ID, 'editors')
expect((sent[1] as CreateGroupCommand).input).toEqual({UserPoolId: POOL_ID, GroupName: 'editors', Precedence: 5})
expect(sent[2]).toBeInstanceOf(DeleteGroupCommand)
})
test('validates a group before creating it', async () => {
const {client, sent} = stubCognito(() => ({}))
const adapter = new AwsUserPoolsAdapter(client)
await expect(adapter.createUserPoolGroup(POOL_ID, {name: 'has space'})).rejects.toBeInstanceOf(ValidationError)
await expect(adapter.createUserPoolGroup(POOL_ID, {name: 'ok', precedence: -1})).rejects.toBeInstanceOf(ValidationError)
expect(sent).toHaveLength(0)
})
test('test sign-in returns decoded claims, adding SECRET_HASH for a client with a secret', async () => {
const {client, sent} = stubCognito((command) => {
if (command instanceof DescribeUserPoolClientCommand) return {UserPoolClient: {ClientId: 'client-1', ClientSecret: 'shh'}}
if (command instanceof InitiateAuthCommand) return {AuthenticationResult: {ExpiresIn: 3600, IdToken: jwt({sub: 's-1', email: 'alice@example.com', token_use: 'id'}), AccessToken: jwt({sub: 's-1', token_use: 'access'})}}
return {}
})
const result = await new AwsUserPoolsAdapter(client).testUserPoolSignIn(POOL_ID, {clientId: 'client-1', username: 'alice@example.com', password: 'Passw0rd!'})
expect(result).toEqual({outcome: 'signed-in', expiresIn: 3600, idToken: {sub: 's-1', email: 'alice@example.com', token_use: 'id'}, accessToken: {sub: 's-1', token_use: 'access'}})
const auth = (sent[1] as InitiateAuthCommand).input
expect(auth.AuthFlow).toBe('USER_PASSWORD_AUTH')
expect(auth.AuthParameters?.SECRET_HASH).toBe(createHmac('sha256', 'shh').update('alice@example.comclient-1').digest('base64'))
})
test('test sign-in reports a wrong password or a challenge as its outcome', async () => {
const refusing = stubCognito((command) => {
if (command instanceof InitiateAuthCommand) throw Object.assign(awsError('NotAuthorizedException'), {message: 'Incorrect username or password.'})
return {UserPoolClient: {ClientId: 'client-1'}}
})
expect(await new AwsUserPoolsAdapter(refusing.client).testUserPoolSignIn(POOL_ID, {clientId: 'client-1', username: 'a', password: 'x'}))
.toEqual({outcome: 'rejected', code: 'NotAuthorizedException', message: 'Incorrect username or password.'})
expect((refusing.sent[1] as InitiateAuthCommand).input.AuthParameters?.SECRET_HASH).toBeUndefined()
const challenging = stubCognito((command) => command instanceof InitiateAuthCommand ? {ChallengeName: 'NEW_PASSWORD_REQUIRED'} : {UserPoolClient: {}})
expect(await new AwsUserPoolsAdapter(challenging.client).testUserPoolSignIn(POOL_ID, {clientId: 'c', username: 'a', password: 'x'}))
.toEqual({outcome: 'challenge', challengeName: 'NEW_PASSWORD_REQUIRED'})
})
})
describe('app client OAuth settings', () => {
const CLIENT_ID = '40808d60e188483d876bbbf646'
const stored = {
UserPoolId: POOL_ID, ClientId: CLIENT_ID, ClientName: 'notes-demo-web',
ExplicitAuthFlows: ['ALLOW_USER_PASSWORD_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH'],
PreventUserExistenceErrors: 'ENABLED', RefreshTokenValidity: 30, EnableTokenRevocation: true,
SupportedIdentityProviders: ['COGNITO'],
}
function clientStub() {
return stubCognito((command) => {
if (command instanceof DescribeUserPoolClientCommand) return {UserPoolClient: stored}
if (command instanceof UpdateUserPoolClientCommand) return {UserPoolClient: {...stored, ...command.input}}
return {}
})
}
test('reads a client with no OAuth set up', async () => {
const settings = await new AwsUserPoolsAdapter(clientStub().client).describeUserPoolClient(POOL_ID, CLIENT_ID)
expect(settings).toEqual({
clientId: CLIENT_ID, clientName: 'notes-demo-web', hasSecret: false,
explicitAuthFlows: ['ALLOW_USER_PASSWORD_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH'],
callbackUrls: [], logoutUrls: [], oauthFlows: [], oauthScopes: [], identityProviders: ['COGNITO'],
})
})
test('turns on the code flow and sends the client\'s other settings back unchanged', async () => {
const {client, sent} = clientStub()
const settings = await new AwsUserPoolsAdapter(client).updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {
callbackUrls: ['http://localhost:4600/api/auth/callback', ' http://localhost:4600/api/auth/callback '],
logoutUrls: ['http://localhost:4600/'],
oauthFlows: ['code'], oauthScopes: ['openid', 'email'], identityProviders: ['COGNITO'],
})
const update = sent.find((command) => command instanceof UpdateUserPoolClientCommand) as UpdateUserPoolClientCommand
expect(update.input).toMatchObject({
ClientName: 'notes-demo-web', ExplicitAuthFlows: ['ALLOW_USER_PASSWORD_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH'],
PreventUserExistenceErrors: 'ENABLED', RefreshTokenValidity: 30, EnableTokenRevocation: true,
CallbackURLs: ['http://localhost:4600/api/auth/callback'], LogoutURLs: ['http://localhost:4600/'],
AllowedOAuthFlows: ['code'], AllowedOAuthScopes: ['openid', 'email'], AllowedOAuthFlowsUserPoolClient: true,
SupportedIdentityProviders: ['COGNITO'],
})
expect(settings.oauthFlows).toEqual(['code'])
})
test('applies Cognito\'s rules before calling it', async () => {
const {client, sent} = clientStub()
const adapter = new AwsUserPoolsAdapter(client)
const valid = {callbackUrls: ['https://app.example/callback'], logoutUrls: [], oauthFlows: ['code' as const], oauthScopes: ['openid'], identityProviders: ['COGNITO']}
await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, callbackUrls: ['http://app.example/callback']})).rejects.toThrow(/https/)
await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, callbackUrls: ['https://app.example/#done']})).rejects.toThrow(/fragment/)
await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, callbackUrls: []})).rejects.toThrow(/callback URL/)
await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, oauthScopes: []})).rejects.toThrow(/scope/)
await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, oauthFlows: ['code', 'client_credentials']})).rejects.toThrow(ValidationError)
await expect(adapter.updateUserPoolClientOAuth(POOL_ID, CLIENT_ID, {...valid, oauthFlows: ['client_credentials']})).rejects.toThrow(/secret/)
expect(sent.some((command) => command instanceof UpdateUserPoolClientCommand)).toBe(false)
})
})