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:
parent
efd404e9aa
commit
00653bb846
4 changed files with 107 additions and 2 deletions
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue