Merge branch 'main' into features/deits

This commit is contained in:
Jean-Sébastien Herbaux 2023-01-25 16:34:56 +01:00 committed by GitHub
commit e10ac14726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 246 additions and 178 deletions

View File

@ -198,38 +198,73 @@ const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds, transaction
return;
}
// Handle databases that don't support window function ROW_NUMBER (here it's MySQL 5)
if (!strapi.db.dialect.supportsWindowFunctions()) {
await cleanOrderColumnsForOldDatabases({ id, attribute, db, inverseRelIds, transaction: trx });
return;
}
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
const update = [];
const updateBinding = [];
const select = ['??'];
const selectBinding = ['id'];
const where = [];
const whereBinding = [];
if (hasOrderColumn(attribute) && id) {
update.push('?? = b.src_order');
updateBinding.push(orderColumnName);
select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS src_order');
selectBinding.push(joinColumn.name, orderColumnName);
where.push('?? = ?');
whereBinding.push(joinColumn.name, id);
}
if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
update.push('?? = b.inv_order');
updateBinding.push(inverseOrderColumnName);
select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS inv_order');
selectBinding.push(inverseJoinColumn.name, inverseOrderColumnName);
where.push(`?? IN (${inverseRelIds.map(() => '?').join(', ')})`);
whereBinding.push(inverseJoinColumn.name, ...inverseRelIds);
}
switch (strapi.db.dialect.client) {
case 'mysql':
await cleanOrderColumnsForInnoDB({ id, attribute, db, inverseRelIds, transaction: trx });
// Here it's MariaDB and MySQL 8
await db
.getConnection()
.raw(
`UPDATE
?? as a,
(
SELECT ${select.join(', ')}
FROM ??
WHERE ${where.join(' OR ')}
) AS b
SET ${update.join(', ')}
WHERE b.id = a.id`,
[joinTable.name, ...selectBinding, joinTable.name, ...whereBinding, ...updateBinding]
)
.transacting(trx);
break;
/*
UPDATE
:joinTable: as a,
(
SELECT
id,
ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
FROM :joinTable:
WHERE :joinColumn: = :id OR :inverseJoinColumn: IN (:inverseRelIds)
) AS b
SET :orderColumn: = b.src_order, :inverseOrderColumn: = b.inv_order
WHERE b.id = a.id;
*/
default: {
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
const update = [];
const updateBinding = [];
const select = ['??'];
const selectBinding = ['id'];
const where = [];
const whereBinding = [];
if (hasOrderColumn(attribute) && id) {
update.push('?? = b.src_order');
updateBinding.push(orderColumnName);
select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS src_order');
selectBinding.push(joinColumn.name, orderColumnName);
where.push('?? = ?');
whereBinding.push(joinColumn.name, id);
}
if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
update.push('?? = b.inv_order');
updateBinding.push(inverseOrderColumnName);
select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS inv_order');
selectBinding.push(inverseJoinColumn.name, inverseOrderColumnName);
where.push(`?? IN (${inverseRelIds.map(() => '?').join(', ')})`);
whereBinding.push(inverseJoinColumn.name, ...inverseRelIds);
}
const joinTableName = addSchema(joinTable.name);
// raw query as knex doesn't allow updating from a subquery
@ -249,17 +284,17 @@ const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds, transaction
.transacting(trx);
/*
`UPDATE :joinTable: as a
SET :orderColumn: = b.src_order, :inverseOrderColumn: = b.inv_order
FROM (
SELECT
id,
ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
FROM :joinTable:
WHERE :joinColumn: = :id OR :inverseJoinColumn: IN (:inverseRelIds)
) AS b
WHERE b.id = a.id`,
UPDATE :joinTable: as a
SET :orderColumn: = b.src_order, :inverseOrderColumn: = b.inv_order
FROM (
SELECT
id,
ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
FROM :joinTable:
WHERE :joinColumn: = :id OR :inverseJoinColumn: IN (:inverseRelIds)
) AS b
WHERE b.id = a.id;
*/
}
}
@ -267,9 +302,9 @@ const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds, transaction
/*
* Ensure that orders are following a 1, 2, 3 sequence, without gap.
* The use of a temporary table instead of a window function makes the query compatible with MySQL 5 and prevents some deadlocks to happen in innoDB databases
* The use of a session variable instead of a window function makes the query compatible with MySQL 5
*/
const cleanOrderColumnsForInnoDB = async ({
const cleanOrderColumnsForOldDatabases = async ({
id,
attribute,
db,
@ -279,96 +314,68 @@ const cleanOrderColumnsForInnoDB = async ({
const { joinTable } = attribute;
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
const now = new Date().valueOf();
const randomHex = randomBytes(16).toString('hex');
const randomSuffix = `${new Date().valueOf()}_${randomBytes(16).toString('hex')}`;
if (hasOrderColumn(attribute) && id) {
const tempOrderTableName = `orderTable_${now}_${randomHex}`;
try {
await db.connection
.raw(
`
CREATE TABLE :tempOrderTableName:
SELECT
id,
(
SELECT count(*)
FROM :joinTableName: b
WHERE a.:orderColumnName: >= b.:orderColumnName: AND a.:joinColumnName: = b.:joinColumnName: AND a.:joinColumnName: = :id
) AS src_order
FROM :joinTableName: a
WHERE a.:joinColumnName: = :id
`,
{
tempOrderTableName,
joinTableName: joinTable.name,
orderColumnName,
joinColumnName: joinColumn.name,
id,
}
)
.transacting(trx);
// raw query as knex doesn't allow updating from a subquery
// https://github.com/knex/knex/issues/2504
await db.connection
.raw(
`UPDATE ?? as a, (SELECT * FROM ??) AS b
SET ?? = b.src_order
WHERE a.id = b.id`,
[joinTable.name, tempOrderTableName, orderColumnName]
)
.transacting(trx);
} finally {
await db.connection.raw(`DROP TABLE IF EXISTS ??`, [tempOrderTableName]).transacting(trx);
}
// raw query as knex doesn't allow updating from a subquery
// https://github.com/knex/knex/issues/2504
const orderVar = `order_${randomSuffix}`;
await db.connection.raw(`SET @${orderVar} = 0;`).transacting(trx);
await db.connection
.raw(
`UPDATE :joinTableName: as a, (
SELECT id, (@${orderVar}:=@${orderVar} + 1) AS src_order
FROM :joinTableName:
WHERE :joinColumnName: = :id
ORDER BY :orderColumnName:
) AS b
SET :orderColumnName: = b.src_order
WHERE a.id = b.id
AND a.:joinColumnName: = :id`,
{
joinTableName: joinTable.name,
orderColumnName,
joinColumnName: joinColumn.name,
id,
}
)
.transacting(trx);
}
if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
const tempInvOrderTableName = `invOrderTable_${now}_${randomHex}`;
try {
await db.connection
.raw(
`
CREATE TABLE ??
SELECT
id,
(
SELECT count(*)
FROM ?? b
WHERE a.?? >= b.?? AND a.?? = b.?? AND a.?? IN (${inverseRelIds
.map(() => '?')
.join(', ')})
) AS inv_order
FROM ?? a
WHERE a.?? IN (${inverseRelIds.map(() => '?').join(', ')})
`,
[
tempInvOrderTableName,
joinTable.name,
inverseOrderColumnName,
inverseOrderColumnName,
inverseJoinColumn.name,
inverseJoinColumn.name,
inverseJoinColumn.name,
...inverseRelIds,
joinTable.name,
inverseJoinColumn.name,
...inverseRelIds,
]
)
.transacting(trx);
await db.connection
.raw(
`UPDATE ?? as a, (SELECT * FROM ??) AS b
SET ?? = b.inv_order
WHERE a.id = b.id`,
[joinTable.name, tempInvOrderTableName, inverseOrderColumnName]
)
.transacting(trx);
} finally {
await db.connection.raw(`DROP TABLE IF EXISTS ??`, [tempInvOrderTableName]).transacting(trx);
}
const orderVar = `order_${randomSuffix}`;
const columnVar = `col_${randomSuffix}`;
await db.connection.raw(`SET @${orderVar} = 0;`).transacting(trx);
await db.connection
.raw(
`UPDATE ?? as a, (
SELECT
id,
@${orderVar}:=CASE WHEN @${columnVar} = ?? THEN @${orderVar} + 1 ELSE 1 END AS inv_order,
@${columnVar}:=?? ??
FROM ?? a
WHERE ?? IN(${inverseRelIds.map(() => '?').join(', ')})
ORDER BY ??, ??
) AS b
SET ?? = b.inv_order
WHERE a.id = b.id
AND a.?? IN(${inverseRelIds.map(() => '?').join(', ')})`,
[
joinTable.name,
inverseJoinColumn.name,
inverseJoinColumn.name,
inverseJoinColumn.name,
joinTable.name,
inverseJoinColumn.name,
...inverseRelIds,
inverseJoinColumn.name,
joinColumn.name,
inverseOrderColumnName,
inverseJoinColumn.name,
...inverseRelIds,
]
)
.transacting(trx);
}
};

View File

@ -43,9 +43,10 @@ const createComponents = async (uid, data) => {
throw new Error('Expected an array to create repeatable component');
}
const components = await Promise.all(
componentValue.map((value) => createComponent(componentUID, value))
);
const components = [];
for (const value of componentValue) {
components.push(await createComponent(componentUID, value));
}
componentBody[attributeName] = components.map(({ id }) => {
return {
@ -77,18 +78,19 @@ const createComponents = async (uid, data) => {
throw new Error('Expected an array to create repeatable component');
}
componentBody[attributeName] = await Promise.all(
dynamiczoneValues.map(async (value) => {
const { id } = await createComponent(value.__component, value);
return {
id,
__component: value.__component,
__pivot: {
field: attributeName,
},
};
})
);
const dynamicZoneData = [];
for (const value of dynamiczoneValues) {
const { id } = await createComponent(value.__component, value);
dynamicZoneData.push({
id,
__component: value.__component,
__pivot: {
field: attributeName,
},
});
}
componentBody[attributeName] = dynamicZoneData;
continue;
}
@ -137,9 +139,10 @@ const updateComponents = async (uid, entityToUpdate, data) => {
throw new Error('Expected an array to create repeatable component');
}
const components = await Promise.all(
componentValue.map((value) => updateOrCreateComponent(componentUID, value))
);
const components = [];
for (const value of componentValue) {
components.push(await updateOrCreateComponent(componentUID, value));
}
componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => {
return {
@ -173,19 +176,19 @@ const updateComponents = async (uid, entityToUpdate, data) => {
throw new Error('Expected an array to create repeatable component');
}
componentBody[attributeName] = await Promise.all(
dynamiczoneValues.map(async (value) => {
const { id } = await updateOrCreateComponent(value.__component, value);
const dynamicZoneData = [];
for (const value of dynamiczoneValues) {
const { id } = await updateOrCreateComponent(value.__component, value);
dynamicZoneData.push({
id,
__component: value.__component,
__pivot: {
field: attributeName,
},
});
}
return {
id,
__component: value.__component,
__pivot: {
field: attributeName,
},
};
})
);
componentBody[attributeName] = dynamicZoneData;
continue;
}
@ -287,14 +290,14 @@ const deleteComponents = async (uid, entityToDelete, { loadComponents = true } =
if (attribute.type === 'component') {
const { component: componentUID } = attribute;
await Promise.all(
_.castArray(value).map((subValue) => deleteComponent(componentUID, subValue))
);
for (const subValue of _.castArray(value)) {
await deleteComponent(componentUID, subValue);
}
} else {
// delete dynamic zone components
await Promise.all(
_.castArray(value).map((subValue) => deleteComponent(subValue.__component, subValue))
);
for (const subValue of _.castArray(value)) {
await deleteComponent(subValue.__component, subValue);
}
}
continue;

View File

@ -228,6 +228,7 @@ module.exports = ({ strapi }) => ({
const formats = await generateResponsiveFormats(fileData);
if (Array.isArray(formats) && formats.length > 0) {
for (const format of formats) {
// eslint-disable-next-line no-continue
if (!format) continue;
uploadPromises.push(uploadResponsiveFormat(format));
}

View File

@ -32,7 +32,9 @@ const syncLocalizations = async (entry, { model }) => {
return strapi.query(model.uid).update({ where: { id }, data: { localizations } });
};
await Promise.all(entry.localizations.map(({ id }) => updateLocalization(id)));
for (const localization of entry.localizations) {
await updateLocalization(localization.id);
}
}
};
@ -56,7 +58,9 @@ const syncNonLocalizedAttributes = async (entry, { model }) => {
return strapi.entityService.update(model.uid, id, { data: nonLocalizedAttributes });
};
await Promise.all(entry.localizations.map(({ id }) => updateLocalization(id)));
for (const localization of entry.localizations) {
await updateLocalization(localization.id);
}
}
};

View File

@ -173,6 +173,21 @@ const forms = {
},
},
],
[
{
intlLabel: {
id: getTrad({ id: 'PopUpForm.Providers.jwksurl.label' }),
defaultMessage: 'JWKS URL',
},
name: 'jwksurl',
type: 'text',
placeholder: textPlaceholder,
size: 12,
validations: {
required: false,
},
},
],
[
{

View File

@ -31,6 +31,7 @@
"@strapi/utils": "4.5.6",
"bcryptjs": "2.4.3",
"grant-koa": "5.4.8",
"jwk-to-pem": "2.0.5",
"jsonwebtoken": "9.0.0",
"koa": "^2.13.4",
"koa2-ratelimit": "^1.1.2",
@ -64,4 +65,4 @@
"required": true,
"kind": "plugin"
}
}
}

View File

@ -2,6 +2,48 @@
const { strict: assert } = require('assert');
const jwt = require('jsonwebtoken');
const jwkToPem = require('jwk-to-pem');
const getCognitoPayload = async ({ idToken, jwksUrl, purest }) => {
const {
header: { kid },
payload,
} = jwt.decode(idToken, { complete: true });
if (!payload || !kid) {
throw new Error('The provided token is not valid');
}
const config = {
cognito: {
discovery: {
origin: jwksUrl.origin,
path: jwksUrl.pathname,
},
},
};
try {
const cognito = purest({ provider: 'cognito', config });
// get the JSON Web Key (JWK) for the user pool
const { body: jwk } = await cognito('discovery').request();
// Get the key with the same Key ID as the provided token
const key = jwk.keys.find(({ kid: jwkKid }) => jwkKid === kid);
const pem = jwkToPem(key);
// https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
const decodedToken = await new Promise((resolve, reject) => {
jwt.verify(idToken, pem, { algorithms: ['RS256'] }, (err, decodedToken) => {
if (err) {
reject();
}
resolve(decodedToken);
});
});
return decodedToken;
} catch (err) {
throw new Error('There was an error verifying the token');
}
};
const getInitialProviders = ({ purest }) => ({
async discord({ accessToken }) {
@ -19,19 +61,14 @@ const getInitialProviders = ({ purest }) => ({
};
});
},
async cognito({ query }) {
// get the id_token
async cognito({ query, providers }) {
const jwksUrl = new URL(providers.cognito.jwksurl);
const idToken = query.id_token;
// decode the jwt token
const tokenPayload = jwt.decode(idToken);
if (!tokenPayload) {
throw new Error('unable to decode jwt token');
} else {
return {
username: tokenPayload['cognito:username'],
email: tokenPayload.email,
};
}
const tokenPayload = await getCognitoPayload({ idToken, jwksUrl, purest });
return {
username: tokenPayload['cognito:username'],
email: tokenPayload.email,
};
},
async facebook({ accessToken }) {
const facebook = purest({ provider: 'facebook' });

View File

@ -14847,7 +14847,7 @@ jwa@^2.0.0:
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jwk-to-pem@^2.0.5:
jwk-to-pem@2.0.5, jwk-to-pem@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/jwk-to-pem/-/jwk-to-pem-2.0.5.tgz#151310bcfbcf731adc5ad9f379cbc8b395742906"
integrity sha512-L90jwellhO8jRKYwbssU9ifaMVqajzj3fpRjDKcsDzrslU9syRbFqfkXtT4B89HYAap+xsxNcxgBSB09ig+a7A==