0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat: remove old same type passwordless connectors before posting (#2516)

This commit is contained in:
Darcy Ye 2022-11-28 12:32:50 +08:00 committed by GitHub
parent efd404e9aa
commit 00653bb846
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 107 additions and 2 deletions

View file

@ -13,6 +13,7 @@ import {
findConnectorById,
countConnectorByConnectorId,
deleteConnectorById,
deleteConnectorByIds,
insertConnector,
updateConnector,
} from './connector.js';
@ -126,6 +127,43 @@ describe('connector queries', () => {
);
});
it('deleteConnectorByIds', async () => {
const rowData = [{ id: 'foo' }, { id: 'bar' }, { id: 'baz' }];
const ids = ['foo', 'bar', 'baz'];
const expectSql = sql`
delete from ${table}
where ${fields.id} in ($1, $2, $3)
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual(ids);
return createMockQueryResult(rowData);
});
await deleteConnectorByIds(ids);
});
it('deleteConnectorByIds should throw with row count does not match length of ids', async () => {
const ids = ['foo', 'bar', 'baz'];
const expectSql = sql`
delete from ${table}
where ${fields.id} in ($1, $2, $3)
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual(ids);
return createMockQueryResult([{ id: 'foo' }, { id: 'bar' }]);
});
await expect(deleteConnectorByIds(ids)).rejects.toMatchError(
new DeletionError(Connectors.table, JSON.stringify({ ids }))
);
});
it('insertConnector', async () => {
const connector = {
...mockConnector,

View file

@ -1,6 +1,6 @@
import type { Connector, CreateConnector } from '@logto/schemas';
import { Connectors } from '@logto/schemas';
import { convertToIdentifiers, manyRows } from '@logto/shared';
import { manyRows, convertToIdentifiers } from '@logto/shared';
import { sql } from 'slonik';
import { buildInsertInto } from '#src/database/insert-into.js';
@ -44,6 +44,17 @@ export const deleteConnectorById = async (id: string) => {
}
};
export const deleteConnectorByIds = async (ids: string[]) => {
const { rowCount } = await envSet.pool.query(sql`
delete from ${table}
where ${fields.id} in (${sql.join(ids, sql`, `)})
`);
if (rowCount !== ids.length) {
throw new DeletionError(Connectors.table, JSON.stringify({ ids }));
}
};
export const insertConnector = buildInsertInto<CreateConnector, Connector>(Connectors, {
returning: true,
});

View file

@ -12,6 +12,7 @@ import {
mockConnector,
mockConnectorFactory,
mockLogtoConnectorList,
mockLogtoConnector,
} from '#src/__mocks__/index.js';
import { defaultConnectorMethods } from '#src/connectors/consts.js';
import type { ConnectorFactory, LogtoConnector } from '#src/connectors/types.js';
@ -21,6 +22,7 @@ import {
findConnectorById,
countConnectorByConnectorId,
deleteConnectorById,
deleteConnectorByIds,
} from '#src/queries/connector.js';
import assertThat from '#src/utils/assert-that.js';
import { createRequester } from '#src/utils/test-utils.js';
@ -42,6 +44,7 @@ jest.mock('#src/queries/connector.js', () => ({
findConnectorById: jest.fn(),
countConnectorByConnectorId: jest.fn(),
deleteConnectorById: jest.fn(),
deleteConnectorByIds: jest.fn(),
insertConnector: jest.fn(async (body: unknown) => body),
}));
@ -221,6 +224,39 @@ describe('connector route', () => {
});
expect(response).toHaveProperty('statusCode', 422);
});
it('should add a new record and delete old records with same connector type when add passwordless connectors', async () => {
loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
{
...mockConnectorFactory,
type: ConnectorType.Sms,
metadata: { ...mockConnectorFactory.metadata, id: 'id0', isStandard: true },
},
]);
getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([
{
dbEntry: { ...mockConnector, connectorId: 'id0' },
metadata: { ...mockMetadata, id: 'id0' },
type: ConnectorType.Sms,
...mockLogtoConnector,
},
]);
const response = await connectorRequest.post('/connectors').send({
connectorId: 'id0',
config: { cliend_id: 'client_id', client_secret: 'client_secret' },
});
expect(response).toHaveProperty('statusCode', 200);
expect(response.body).toMatchObject(
expect.objectContaining({
connectorId: 'id0',
config: {
cliend_id: 'client_id',
client_secret: 'client_secret',
},
})
);
expect(deleteConnectorByIds).toHaveBeenCalledWith(['id']);
});
});
describe('POST /connectors/:id/test', () => {

View file

@ -18,6 +18,7 @@ import {
findConnectorById,
countConnectorByConnectorId,
deleteConnectorById,
deleteConnectorByIds,
insertConnector,
updateConnector,
} from '#src/queries/connector.js';
@ -130,11 +131,30 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
})
);
const insertConnectorId = generateConnectorId();
ctx.body = await insertConnector({
id: generateConnectorId(),
id: insertConnectorId,
...body,
});
/**
* We can have only one working email/sms connector:
* once we insert a new one, old connectors with same type should be deleted.
*/
if (
connectorFactory.type === ConnectorType.Sms ||
connectorFactory.type === ConnectorType.Email
) {
const logtoConnectors = await getLogtoConnectors();
const conflictingConnectorIds = logtoConnectors
.filter(
({ dbEntry: { id }, type }) =>
type === connectorFactory.type && id !== insertConnectorId
)
.map(({ dbEntry: { id } }) => id);
await deleteConnectorByIds(conflictingConnectorIds);
}
return next();
}
);